package com.lime49.lockcrypt;

import com.lime49.lockcrypt.idletimer.*;
import com.lime49.lockcrypt.ui.*;
import java.io.*;
import java.util.*;
import java.util.prefs.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.border.*;
import javax.swing.tree.*;
import java.net.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.sql.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.awt.datatransfer.DataFlavor;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.text.MessageFormat;

import javax.swing.UIManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jgoodies.looks.plastic.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import org.json.*;

public class FrmMainWindow extends JFrame implements ILanguageParent {
    private JFrame mainWindow, searchDialog, frameProgressBar;
    private JPanel maincontent, accountView, searchContainer;
    private JSplitPane splitPane;
    private Comparator<AccountTreeNode> treeComparator;
    private DefaultTreeModel defAccountTreeModel;
    private JScrollPane accountTreeScroll, searchResultsScroll;
    private JMenuBar menuBar;
    private JMenu mnuFile, mnuAdd, mnuImport, mnuExport, mnuView, mnuEdit, mnuDebug;
    private JMenuItem itmEdit, itmRemove, itmSearch, itmRollback, itmPrint, itmRelock, itmSaveDB, itmSynchronize, itmCreateCopy, itmAutoType;
    private JRadioButtonMenuItem itmLargeIcons, itmSmallIcons, itmList, itmTiles, itmSortName, itmSortCreated, itmSortModified, itmSortIcon, itmSortType, itmSortAscending, itmSortDescending;
    private JToolBar toolBar;
    private ToolbarListener toolBarListener;
    private JButton btnRemove, btnEdit, btnSearch, btnPrint, btnRelock, btnViews, btnSynchronize;
    private DropDownButton btnAdd;
    private JPopupMenu accountTreePopup;
    private JTextField txtFind;
    private JPasswordField passSetJCEKey;
    private JLabel lblProgress, searchStatusBar, statusBar;
    private JProgressBar progressBar;
    private JTable groupTable, searchResults;

    public AccountTree accountTree;
    private Account selectedAccount;
    private Group accountTreeRoot;
    private Group selectedGroup;
    private IdleTimer tmrAutoType;
    private boolean refreshDB = false;
    private byte[] sqlEncryptedCheck;
    private Preferences prefs;
    
    private String setDBMode, // text/mysql
                   setFileName, // path to file
                   setDBHost, 
                   setDBUser, 
                   setDBPass, 
                   setDBName, 
                   setSyncFreq, 
                   setLang,
                   setLastExportLocation,
                   setLastDBLocation,
                   setSizePosition,
                   setLaf, // Look'n'feel
                   externalSettingsFile, // used when a filename is passed in the command line arguments
                   setAutoTypeHotkey;
    private char setViewMode, // (S)mall icons/L(I)st/(T)iles/(L)arge icons
                 setSortMode, // (N)ame/(T)ype/(C)reated/(M)odified/(I)con
                 setSortOrder; // (A)scending/(D)escending
    private boolean setFirstLoad,
                    setSecureClipboard,
                    setSizeMaximized,
                    setMinimizeToTray,
                    setEnableFirefox,
                    setEnableAutoType;
    private int setSecureClipDelay,
                setSizeWidth,
                setSizeHeight,
                setAutoLock;
    public String setJCEKey; // the encryption key
    
    private FirefoxSocket firefoxServerThread;
    private AutoTyper autoTyper;
    public ArrayList<AccType> types;
    public ArrayList<Account> accounts;
    public ArrayList<Group> accountGroups;
    private ResourceBundle lang;
    private boolean DEBUG = Constants.DEBUG;
    
    public FrmMainWindow(String[] args) {
        super("LockCrypt");
        try {
        System.out.println(new WildcardURL("http://*.intra.google*/*login.php").matches(new URL("http://google.com/login.php")));
        System.out.println(new WildcardURL("*://*.com").matches(new URL("http://groups.google.com/group1")));
        }catch(Exception ex) {}
        KeyStroke deleteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0, false);
        Action deleteAction = new AbstractAction() {
            // close the frame when the user presses escape
            public void actionPerformed(ActionEvent e) {
                removeSelected();
            }
        };
        this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(deleteKeyStroke, "DELETE");
        this.getRootPane().getActionMap().put("DELETE", deleteAction);
        try {
            URL iconURL = this.getClass().getResource("images/icon_16.png");
            this.setIconImage(new ImageIcon(iconURL).getImage());
        } catch(Exception ignore) {/**/}
        prefs = Preferences.userNodeForPackage(this.getClass());
        types = new ArrayList<AccType>();
        accounts = new ArrayList<Account>();
        accountGroups = new ArrayList<Group>();
          
        if(args.length > 0) {
            File settingsFile = new File(args[0]);
            if(settingsFile.exists()) {
                externalSettingsFile = args[0];
            } else {
                MessageFormat fmt = new MessageFormat("Could not open file ''{0}', did you specify the full path?");
                JOptionPane.showMessageDialog(null, fmt.format(new Object[] { args[0] }), "Error", JOptionPane.ERROR_MESSAGE);
            }
        }
        loadSettings();
        
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent we) {
                shutdown();
            }
        });
        if(SystemTray.isSupported()) {
            try {
                final TrayIcon icon = new TrayIcon(Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("images/icon_16.png")),"LockCrypt");
                icon.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        mainWindow.setVisible(true);
                        mainWindow.setExtendedState(FrmMainWindow.NORMAL);
                        SystemTray.getSystemTray().remove(icon);
                    }

                });
                addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowIconified(WindowEvent e) {
                        if(setMinimizeToTray) {
                            mainWindow.setVisible(false);
                            try {
                                SystemTray.getSystemTray().add(icon);
                            } catch (AWTException e1) {
                                e1.printStackTrace();
                            }
                        }
                    }
                });
            } catch(Exception e) {}
        }

        maincontent = new JPanel();
            this.setContentPane(maincontent);
            maincontent.setLayout(new BorderLayout());
            createMenuToolBars();
            statusBar = new JLabel();
                statusBar.setHorizontalTextPosition(SwingConstants.RIGHT);
                statusBar.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
                maincontent.add(statusBar, BorderLayout.SOUTH);
                setStatusText("");
            accountTreeRoot = new Group("LockCrypt", null, false);
            accountTree = new AccountTree(accountTreeRoot, this);
                accountTree.addMouseListener(new AccountTreeMouseListener());
                accountTree.setCellRenderer(new AccountTreeRenderer());
                accountTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
            defAccountTreeModel = (DefaultTreeModel)accountTree.getModel();
            accountTreeScroll = new JScrollPane(accountTree);
                accountTreeScroll.setBorder(null);
                accountTreeScroll.setViewportBorder(null);
                accountTreeScroll.setPreferredSize(new Dimension(200,300));

            accountView = new JPanel();
                accountView.setLayout(new BorderLayout());
                accountView.setBackground(Color.WHITE);
                accountView.setMaximumSize((new Dimension(1000,1000)));
            splitPane  = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, null, accountView);
                splitPane.setDividerSize(5);
                splitPane.setContinuousLayout(true);
                splitPane.setResizeWeight(1);
                maincontent.add(splitPane, BorderLayout.CENTER);
            this.setMinimumSize(new Dimension(500, 300));
            this.setPreferredSize(new Dimension(setSizeWidth,setSizeHeight));
            if(setSizeMaximized) {
                this.setExtendedState(JFrame.MAXIMIZED_BOTH);
            } else {
                String[] coords = setSizePosition.split(",");
                this.setLocation(Integer.parseInt(coords[0]), Integer.parseInt(coords[1]));
            }
            mainWindow = this;
                
        setMenuItems(false);
        this.pack();
        this.setVisible(true);
              
        String settingsFileName;
        if(externalSettingsFile != null && externalSettingsFile.length() > 0) {
            settingsFileName = externalSettingsFile;
        } else {
            settingsFileName = setFileName;
        }
        File settingsFile = new File(settingsFileName);
        if(!settingsFile.exists()) {
            setFirstLoad = true;
        }

        //JOptionPane.showMessageDialog(null, "First load:"+setFirstLoad+"\nArgs:'"+(args.length >= 1 ? args.length >=2 ? args[0]+"\n"+args[1] : args[0] : "")+"'\nFile:"+setFileName);
        tmrAutoType = new IdleTimer(this, setAutoLock);
        if(setFirstLoad) {
            showWelcome();
        } else {
            promptPass();
        }
        if(setEnableFirefox) {
            startFirefoxServer();
        }
        if(setEnableAutoType && System.getProperty("os.name").toLowerCase().startsWith("windows")) {
            autoTyper = new AutoTyper(this);
            autoTyper.launchAutoTyper();
            registerAutoTypeHotkey();
        }
    }
    
    /**
     * Creates a menu bar and adds it to the window.
     */
    private void createMenuToolBars() {
        this.setJMenuBar(createMenuBar());
        toolBarListener = new ToolbarListener();
        if(toolBar != null) {
            maincontent.remove(toolBar);
        }
        toolBar = createToolbar();    
        maincontent.add(toolBar, BorderLayout.PAGE_START);
        this.pack();
        this.repaint();
    }
    
    /**
     * Creates a menu bar and fills it with menu items
     * @return The JMenuBar
     */
    private JMenuBar createMenuBar() {
        menuBar = new JMenuBar();
        menuBar.putClientProperty(PlasticLookAndFeel.IS_3D_KEY, Boolean.TRUE); // Custom LookAndFeel
        
        Icon addIcon = null, delIcon = null, editIcon = null, optionsIcon = null, searchIcon = null, printIcon = null, lockIcon = null,
             passwordIcon = null, view_largeIcon = null, view_smallIcon = null, view_listIcon = null, view_tilesIcon = null, createCopyIcon = null,
             autoTypeIcon = null;
        try {
            URL addIconURL = this.getClass().getResource("images/add_16.png");
            addIcon = new ImageIcon(addIconURL, lang.getString("ADD"));
            URL delIconURL = this.getClass().getResource("images/delete_16.png");
            delIcon = new ImageIcon(delIconURL, lang.getString("DELETE"));
            URL editIconURL = this.getClass().getResource("images/edit_16.png");
            editIcon = new ImageIcon(editIconURL, lang.getString("EDIT"));
            URL optionsIconURL = this.getClass().getResource("images/config_16.png");
            optionsIcon = new ImageIcon(optionsIconURL, lang.getString("OPTIONS"));
            URL searchIconURL = this.getClass().getResource("images/search_16.png");
            searchIcon = new ImageIcon(searchIconURL, lang.getString("SEARCH"));
            URL printIconURL = this.getClass().getResource("images/print_16.png");
            printIcon = new ImageIcon(printIconURL, lang.getString("PRINT"));
            URL lockIconURL = this.getClass().getResource("images/lock_16.png");
            lockIcon = new ImageIcon(lockIconURL, lang.getString("RELOCK"));
            URL passwordIconURL = this.getClass().getResource("images/password_16.png");
            passwordIcon = new ImageIcon(passwordIconURL, lang.getString("PWGEN"));
            URL view_largeURL = this.getClass().getResource("images/icons_large_16.png");
            view_largeIcon = new ImageIcon(view_largeURL, lang.getString("VIEW_LARGEICONS"));
            URL view_smallURL = this.getClass().getResource("images/icons_small_16.png");
            view_smallIcon = new ImageIcon(view_smallURL, lang.getString("VIEW_SMALLICONS"));
            URL view_listURL = this.getClass().getResource("images/icons_list_16.png");
            view_listIcon = new ImageIcon(view_listURL, lang.getString("VIEW_LIST"));
            URL view_tilesURL = this.getClass().getResource("images/icons_tiles_16.png");
            view_tilesIcon = new ImageIcon(view_tilesURL, lang.getString("VIEW_TILES"));
            URL createCopyIconURL = this.getClass().getResource("images/copy_16.png");
            createCopyIcon = new ImageIcon(createCopyIconURL, lang.getString("CREATECOPY"));
            URL autoTypeIconURL = this.getClass().getResource("images/autotype_16.png");
            autoTypeIcon = new ImageIcon(autoTypeIconURL, lang.getString("AUTOTYPESELECTED"));
        } catch(Exception ignore) {}
        mnuFile = new JMenu(lang.getString("MNU_FILE"));
            menuBar.add(mnuFile);
            mnuFile.setMnemonic('F');
            mnuAdd = new JMenu(lang.getString("ADD"));
                mnuAdd.setMnemonic('A');
                mnuFile.add(mnuAdd);
                JMenuItem itmAddAccount = new JMenuItem(lang.getString("ADDACCOUNT"), addIcon);
                    itmAddAccount.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { showAddDialog(new Account(), false); } 
                    });
                    mnuAdd.add(itmAddAccount);
                JMenuItem itmAddGroup = new JMenuItem(lang.getString("ADDGROUP"), addIcon);
                    itmAddGroup.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { showAddGroupDialog(new Group(), false); } 
                    });
                    mnuAdd.add(itmAddGroup);
            itmPrint = new JMenuItem(lang.getString("PRINT"), printIcon);
                itmPrint.setMnemonic('P');
                itmPrint.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { showPrintDialog(); } 
                });
                mnuFile.add(itmPrint);
            itmRelock = new JMenuItem(lang.getString("RELOCK"), lockIcon);
                itmRelock.setMnemonic('R');
                itmRelock.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { relock(); } 
                });
                mnuFile.add(itmRelock);
            itmSaveDB = new JMenuItem( lang.getString("SAVEDATABASE"));
                mnuFile.add(itmSaveDB);
                itmSaveDB.setMnemonic('S');
                itmSaveDB.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { saveDatabase(null,false,true); }
                });
            mnuImport = new JMenu(lang.getString("IMPORT"));
                mnuImport.setMnemonic('I');
                mnuFile.add(mnuImport);
                JMenuItem itmImportFromXML = new JMenuItem(lang.getString("XMLFILE"));
                    itmImportFromXML.setMnemonic('X');
                    itmImportFromXML.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showImportDialog(FrmImportExport.DataType.XML); }
                        });
                    mnuImport.add(itmImportFromXML);
                JMenuItem itmImportFromEncryptedXML = new JMenuItem(lang.getString("ENCRYPTEDXMLFILE"));
                    itmImportFromEncryptedXML.setMnemonic('E');
                    itmImportFromEncryptedXML.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showImportDialog(FrmImportExport.DataType.EncryptedXML); }
                        });
                    mnuImport.add(itmImportFromEncryptedXML);
                JMenuItem itmImportFromLC = new JMenuItem(lang.getString("LOCKCRYPTDATABASE"));
                    itmImportFromLC.setMnemonic('L');
                    itmImportFromLC.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showImportDialog(FrmImportExport.DataType.LockCrypt); }
                        });
                    mnuImport.add(itmImportFromLC);
                JMenuItem itmImportFromLCCompatibility = new JMenuItem(lang.getString("LOCKCRYPTDATABASEC"));
                    itmImportFromLCCompatibility.setMnemonic('C');
                    itmImportFromLCCompatibility.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showImportDialog(FrmImportExport.DataType.LockCryptCompatibility); }
                        });
                    mnuImport.add(itmImportFromLCCompatibility);
            mnuExport = new JMenu(lang.getString("EXPORT"));
                mnuExport.setMnemonic('E');
                mnuFile.add(mnuExport);
                JMenuItem itmExportToXML = new JMenuItem(lang.getString("XMLFILE"));
                    itmExportToXML.setMnemonic('X');
                    itmExportToXML.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.XML); }
                        });
                    mnuExport.add(itmExportToXML);
                JMenuItem itmExportToCSV = new JMenuItem(lang.getString("CSVFILE"));
                    itmExportToCSV.setMnemonic('C');
                    itmExportToCSV.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.CSV); }
                        });
                    mnuExport.add(itmExportToCSV);
                JMenuItem itmExportToEncryptedXML = new JMenuItem(lang.getString("ENCRYPTEDXMLFILE"));
                    itmExportToEncryptedXML.setMnemonic('E');
                    itmExportToEncryptedXML.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.EncryptedXML); }
                        });
                    mnuExport.add(itmExportToEncryptedXML);
                JMenuItem itmExportToHTML = new JMenuItem(lang.getString("HTMLFILE"));
                    itmExportToHTML.setMnemonic('H');
                    itmExportToHTML.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.HTML); }
                        });
                    mnuExport.add(itmExportToHTML);
                JMenuItem itmExportToLC = new JMenuItem(lang.getString("LOCKCRYPTDATABASE"));
                    itmExportToLC.setMnemonic('L');
                    itmExportToLC.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.LockCrypt); }
                        });
                    mnuExport.add(itmExportToLC);
                JMenuItem itmExportToLCCompatibility = new JMenuItem(lang.getString("LOCKCRYPTDATABASEC"));
                    itmExportToLCCompatibility.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.LockCryptCompatibility); }
                        });
                    mnuExport.add(itmExportToLCCompatibility);
                JMenuItem itmExportToTxt = new JMenuItem(lang.getString("TXTFILE"));
                    itmExportToTxt.setMnemonic('T');
                    itmExportToTxt.addActionListener(new ActionListener() {
                           public void actionPerformed(ActionEvent e) { showExportDialog(FrmImportExport.DataType.TXT); }
                        });
                    mnuExport.add(itmExportToTxt);
              mnuFile.addSeparator();
              itmSynchronize = new JMenuItem(lang.getString("SYNCHRONIZE"), null);
                itmSynchronize.setMnemonic('Y');
                itmSynchronize.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { launchSyncUtility(); } 
                });
                mnuFile.add(itmSynchronize);
            mnuFile.addSeparator();
            JMenuItem itmExit = new JMenuItem(lang.getString("EXIT"));
                mnuFile.add(itmExit);
                itmExit.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { shutdown(); } 
                });
        
        mnuEdit = new JMenu(lang.getString("MNU_EDIT"));
            menuBar.add(mnuEdit);
            mnuEdit.setMnemonic('E');
            itmCreateCopy = new JMenuItem(lang.getString("CREATECOPY"), createCopyIcon);
                itmCreateCopy.setMnemonic('y');
                itmCreateCopy.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { createCopy(selectedAccount); } 
                });
                mnuEdit.add(itmCreateCopy);
            itmEdit = new JMenuItem(lang.getString("EDITACCOUNT"), editIcon);
                itmEdit.setMnemonic('i');
                itmEdit.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { showAddDialog(selectedAccount, true); } 
                });
                mnuEdit.add(itmEdit);
            itmRemove = new JMenuItem(lang.getString("DELACCOUNT"), delIcon);
                itmRemove.setMnemonic('D');
                itmRemove.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { removeAccount(selectedAccount); } 
                });
                mnuEdit.add(itmRemove);
            itmSearch = new JMenuItem(lang.getString("SEARCH"), searchIcon);
                itmSearch.setMnemonic('S');
                itmSearch.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { showSearchDialog(); } 
                });
                mnuEdit.add(itmSearch);
                
        mnuView = new JMenu(lang.getString("MNU_VIEW"));
        ButtonGroup grpViews = new ButtonGroup();
            menuBar.add(mnuView);
            mnuView.setMnemonic('V');
            ViewModeListener viewListener = new ViewModeListener();
            itmLargeIcons = new JRadioButtonMenuItem(lang.getString("VIEW_LARGEICONS"), view_largeIcon);
                mnuView.add(itmLargeIcons);
                grpViews.add(itmLargeIcons);
                itmLargeIcons.setMnemonic('L');
                itmLargeIcons.addActionListener(viewListener);
                if(setViewMode== 'L') { // Largeicons, Smallicons, lIst, Tiles
                    itmLargeIcons.setSelected(true);
                }
            itmSmallIcons = new JRadioButtonMenuItem(lang.getString("VIEW_SMALLICONS"), view_smallIcon);
                mnuView.add(itmSmallIcons);
                grpViews.add(itmSmallIcons);
                itmSmallIcons.setMnemonic('S');
                itmSmallIcons.addActionListener(viewListener);
                if(setViewMode == 'S') {
                    itmSmallIcons.setSelected(true);
                }
            itmList = new JRadioButtonMenuItem(lang.getString("VIEW_LIST"), view_listIcon);
                mnuView.add(itmList);
                grpViews.add(itmList);
                itmList.setMnemonic('i');
                itmList.addActionListener(viewListener);
                if(setViewMode == 'I') {
                    itmList.setSelected(true);
                }
            itmTiles = new JRadioButtonMenuItem(lang.getString("VIEW_TILES"), view_tilesIcon);
                mnuView.add(itmTiles);
                grpViews.add(itmTiles);
                itmTiles.setMnemonic('e');
                itmTiles.addActionListener(viewListener);
                if(setViewMode == 'T') {
                    itmTiles.setSelected(true);
                }
            JMenu mnuSort = new JMenu(lang.getString("SORTBY"));
            ButtonGroup grpSort = new ButtonGroup();
                mnuView.add(mnuSort);
                mnuSort.setMnemonic('S');
                SortModeListener sortListener = new SortModeListener();
                itmSortName = new JRadioButtonMenuItem(lang.getString("NAME"));
                    mnuSort.add(itmSortName);
                    grpSort.add(itmSortName);
                    itmSortName.setMnemonic('N');
                    itmSortName.addActionListener(sortListener);
                    if(setSortMode == 'N') {
                        itmSortName.setSelected(true);
                    }
                itmSortType = new JRadioButtonMenuItem(lang.getString("TYPE"));
                    mnuSort.add(itmSortType);
                    grpSort.add(itmSortType);
                    itmSortType.setMnemonic('T');
                    itmSortType.addActionListener(sortListener);
                    if(setSortMode == 'T') {
                        itmSortType.setSelected(true);
                    }
                itmSortIcon = new JRadioButtonMenuItem(lang.getString("ICON"));
                    mnuSort.add(itmSortIcon);
                    grpSort.add(itmSortIcon);
                    itmSortIcon.setMnemonic('I');
                    itmSortIcon.addActionListener(sortListener);
                    if(setSortMode == 'I') {
                        itmSortIcon.setSelected(true);
                    }
                itmSortCreated = new JRadioButtonMenuItem(lang.getString("DATECREATED"));
                    mnuSort.add(itmSortCreated);
                    grpSort.add(itmSortCreated);
                    itmSortCreated.setMnemonic('C');
                    itmSortCreated.addActionListener(sortListener);
                    if(setSortMode == 'C') {
                        itmSortCreated.setSelected(true);
                    }
                itmSortModified = new JRadioButtonMenuItem(lang.getString("DATEMODIFIED"));
                    mnuSort.add(itmSortModified);
                    grpSort.add(itmSortModified);
                    itmSortModified.setMnemonic('M');
                    itmSortModified.addActionListener(sortListener);
                    if(setSortMode == 'M') {
                        itmSortModified.setSelected(true);
                    }
                 mnuSort.addSeparator();
             ButtonGroup grpOrder = new ButtonGroup();
                itmSortAscending = new JRadioButtonMenuItem(lang.getString("ASCENDING"));
                    mnuSort.add(itmSortAscending);
                    grpOrder.add(itmSortAscending);
                    itmSortAscending.setMnemonic('A');
                    itmSortAscending.addActionListener(sortListener);
                    if(setSortOrder == 'A') {
                        itmSortAscending.setSelected(true);
                    }
                itmSortDescending = new JRadioButtonMenuItem(lang.getString("DESCENDING"));
                    mnuSort.add(itmSortDescending);
                    grpOrder.add(itmSortDescending);
                    itmSortDescending.setMnemonic('D');
                    itmSortDescending.addActionListener(sortListener);
                    if(setSortOrder == 'D') {
                        itmSortDescending.setSelected(true);
                    }
                 
                
        JMenu mnuTools = new JMenu(lang.getString("MNU_TOOLS"));
            menuBar.add(mnuTools);
            mnuTools.setMnemonic('T');
            itmAutoType = new JMenuItem(lang.getString("AUTOTYPESELECTED"), autoTypeIcon);
                itmAutoType.setEnabled(false);
                itmAutoType.setMnemonic('A');
                itmAutoType.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { doAutoType(selectedAccount); }
                });
                mnuTools.add(itmAutoType);
            JMenuItem itmOptions = new JMenuItem(lang.getString("OPTIONS"), optionsIcon);
                itmOptions.setMnemonic('O');
                itmOptions.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { showOptionsDialog(); } 
                });
                mnuTools.add(itmOptions);
            JMenuItem itmPWGen = new JMenuItem(lang.getString("PWGEN"), passwordIcon);
                itmPWGen.setMnemonic('P');
                itmPWGen.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { 
                       FrmPWGen frmPWGen = new FrmPWGen(mainWindow, lang); 
                       Point parentLoc = mainWindow.getLocation();
                       frmPWGen.setLocation(parentLoc.x + ((mainWindow.getWidth() - frmPWGen.getWidth())/2), parentLoc.y + ((mainWindow.getHeight() - frmPWGen.getHeight())/2));
                       frmPWGen.setVisible(true);
                   }
                });
                mnuTools.add(itmPWGen);
            itmRollback = new JMenuItem(lang.getString("ROLLBACK"));
                itmRollback.setMnemonic('R');
                itmRollback.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ROLLBACK_DESC"), lang.getString("ROLLBACK"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) {
                            loadDatabase(null, true);
                        }
                    }
                });
                mnuTools.add(itmRollback);
            JMenuItem itmClearPrefs = new JMenuItem(lang.getString("CLEARPREFS"));
                itmClearPrefs.setMnemonic('C');
                mnuTools.add(itmClearPrefs);
                itmClearPrefs.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("CLEARPREFS_DESC"), lang.getString("CLEARPREFS"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            if(setDBMode.equals("text")) {
                                File fileName = new File(setFileName);
                                fileName.delete();
                            } else if(setDBMode.equals("mysql")) {
                                Connection conn = null;
                                try {
                                    Class.forName("com.mysql.jdbc.Driver").newInstance();
                                    conn = DriverManager.getConnection("jdbc:mysql://"+setDBHost+"/"+setDBName,setDBUser,setDBPass);
                                    Statement s = conn.createStatement();
                                    try {
                                        s.executeUpdate("TRUNCATE TABLE `settings`");
                                        s.executeUpdate("TRUNCATE TABLE `accounts`");
                                        s.executeUpdate("TRUNCATE TABLE `groups`");
                                        s.executeUpdate("TRUNCATE TABLE  `types`");
                                        s.executeUpdate("DROP TABLE IF EXISTS `settings`");
                                        s.executeUpdate("DROP TABLE IF EXISTS `accounts`");
                                        s.executeUpdate("DROP TABLE IF EXISTS `groups`");
                                        s.executeUpdate("DROP TABLE IF EXISTS `types`");
                                    } catch(Exception er) {er.printStackTrace();}
                                } catch(Exception er) {er.printStackTrace();}
                            }
                            try { 
                                setJCEKey = null; 
                                prefs.clear();
                            } catch(Exception ignore) {}
                            accounts = new ArrayList<Account>();
                            accountGroups = new ArrayList<Group>();
                            refreshAccountTree();
                            accountTree.setSelectionRow(0);
                            System.exit(0);
                        }
                    }
                });
        
        JMenu mnuHelp = new JMenu(lang.getString("MNU_HELP"));
            menuBar.add(mnuHelp);
            mnuHelp.setMnemonic('H');
            JMenuItem itmLCHelp = new JMenuItem(lang.getString("LCHELP"));
                mnuHelp.add(itmLCHelp);
                itmLCHelp.setMnemonic('C');
                itmLCHelp.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        try {
                            if(new File(Utils.getJARPath()+"\\LockCrypt\\LockCrypt.chm").exists()) {
                                Runtime.getRuntime().exec("hh.exe "+Utils.getJARPath()+"\\LockCrypt.chm");
                            } else if(new File(System.getProperty("user.dir")+"\\LockCrypt.chm").exists()) {
                                Runtime.getRuntime().exec("hh.exe "+System.getProperty("user.dir")+"\\LockCrypt.chm");
                            } else if(new File(System.getProperty("ProgramFiles")+"\\LockCrypt.chm").exists()) {
                                Runtime.getRuntime().exec("hh.exe "+System.getProperty("ProgramFiles")+"\\LockCrypt\\LockCrypt.chm");
                            } else if(new File(System.getProperty("ProgramFiles(x86)")+"\\LockCrypt.chm").exists()) {
                                Runtime.getRuntime().exec("hh.exe "+System.getProperty("ProgramFiles(x86)")+"\\LockCrypt\\LockCrypt.chm");
                            } else {
                                JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#213: "+lang.getString("ERR_HELPNOTFOUND"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                            }
                        } catch(IOException er) {
                            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#213: "+lang.getString("ERR_HELPNOTFOUND"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                        }
                    }
                });
            JMenuItem itmLCSite = new JMenuItem(lang.getString("LCWEBSITE"));
                mnuHelp.add(itmLCSite);
                itmLCSite.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        Utils.openURL("http://www.lockcrypt.com/");
                    }
                });
            JMenuItem itmAbout = new JMenuItem(lang.getString("ABOUT"));
                mnuHelp.add(itmAbout);
                itmAbout.setMnemonic('A');
                itmAbout.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { showAbout(); }
                });
        KeyStroke debugKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, false);
        Action debugAction = new AbstractAction() {
            // close the frame when the user presses escape
            public void actionPerformed(ActionEvent e) {
                DEBUG = !DEBUG;
                mnuDebug.setVisible(DEBUG);
                menuBar.revalidate();
            }
        }; 
        this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(debugKeyStroke, "D");
        this.getRootPane().getActionMap().put("D", debugAction);
        mnuDebug = new JMenu("Debug");
            mnuDebug.setMnemonic('D');
            menuBar.add(mnuDebug);
            mnuDebug.setVisible(DEBUG);
            JCheckBoxMenuItem itmEnableDebug = new JCheckBoxMenuItem("Enable Debug");
                mnuDebug.add(itmEnableDebug);
                itmEnableDebug.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) {
                       if(((JCheckBoxMenuItem)e.getSource()).isSelected()) {
                           DEBUG = true;
                       } else {
                           DEBUG = false;
                       }
                   } 
                });
            JMenuItem itmprintDB = new JMenuItem("Print DB");
                mnuDebug.add(itmprintDB);
                itmprintDB.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { printdb(); }
                });
            JMenuItem itmPrintSettings = new JMenuItem("Print Settings");
                mnuDebug.add(itmPrintSettings);
                itmPrintSettings.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                    System.out.println("setDBMode: "+setDBMode);
                    System.out.println("setFileName: "+setFileName);
                    System.out.println("setDBHost: "+setDBHost);
                    System.out.println("setDBUser: "+setDBUser);
                    System.out.println("setDBName: "+setDBName);
                    System.out.println("setSyncFreq: "+setSyncFreq);
                    System.out.println("setLang: "+setLang);
                    System.out.println("setViewMode: "+setViewMode);
                    System.out.println("setSortMode: "+setSortMode);
                    System.out.println("setSortOrder: "+setSortOrder);
                    System.out.println("setFirstLoad: "+setFirstLoad);
                    System.out.println("setSecureClipboard: "+setSecureClipboard);
                    System.out.println("setSecureClipDelay: "+setSecureClipDelay);
                    }
                });
            mnuDebug.addSeparator();
            JMenuItem itmStartFF = new JMenuItem("Start Firefox Server");
                mnuDebug.add(itmStartFF);
                itmStartFF.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { startFirefoxServer(); }
                });
            JMenuItem itmStopFF = new JMenuItem("Stop Firefox server");
                mnuDebug.add(itmStopFF);
                itmStopFF.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { stopFirefoxServer(); }
                });
            JMenuItem itmLoadDB = new JMenuItem("Load database");
                mnuDebug.add(itmLoadDB);
                itmLoadDB.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { loadDatabase(null, true); }
                });
            JMenuItem itmFindWindows = new JMenuItem("Find Windows");
                mnuDebug.add(itmFindWindows);
                itmFindWindows.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { if(autoTyper != null) {
                        String[] windows = autoTyper.getWindows();
                        JDialog windowFrame = new JDialog(mainWindow, "Windows", true);
                        windowFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                        JComboBox cboWindows = new JComboBox(windows);
                        windowFrame.add(cboWindows);
                        windowFrame.pack();
                        windowFrame.setLocationRelativeTo(mainWindow);
                        windowFrame.setVisible(true);
                    } }
                });
            JMenuItem itmCheckURLMatches = new JMenuItem("Test URL matches");
                mnuDebug.add(itmCheckURLMatches);
                itmCheckURLMatches.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) { UtilsTest.wildcardURLs(); }
                });
        return menuBar;
    }

    /**
     * Sets the text on the statusbar and changes the icon to a 'disconnected' icon if the text is blank
     * @param text The text to set
     */
    private void setStatusText(String text) {
        Icon ico;
        if(text.length() > 0) {
            ico = new ImageIcon(this.getClass().getResource("images/database_auth_16.png"), text);
        } else {
            ico = new ImageIcon(this.getClass().getResource("images/database_error_16.png"), text);
        }
        statusBar.setText(text);
        statusBar.setIcon(ico);
    }

    /**
     * Creates a copy of accountGroups arraylist,
     * then works through the list, checking if each node's parent has been 
     * added. If it has, it adds the node to it's parent.
     */
    private void refreshTreeGroups() {
        ArrayList<Group> toBeAdded = accountGroups;
        ArrayList<Group> tempList;
        while(toBeAdded.size()>0) {
            tempList = new ArrayList<Group>();
            for(int i=0;i<toBeAdded.size();i++) {
                Group thisGroup = toBeAdded.get(i);
                thisGroup.removeAllChildren();
                thisGroup.setComparator(treeComparator);
                if(!thisGroup.getIsDeleted()) {
                    DefaultMutableTreeNode parentNode = getNodeFromName(accountTreeRoot, thisGroup.getParentName());
                    if(parentNode != null) {
                        addNodeToGroup(thisGroup, parentNode, thisGroup.getParentName());
                    } else {
                        tempList.add(thisGroup);
                    }
                }
            }
            toBeAdded = tempList;
        }
    }
    
    /**
     * Removes all child nodes from the account tree, then works through the accounts list, adding
     * each account to it's parent group.
     */
    public void refreshAccountTree() {
        accountTreeRoot.removeAllChildren();
        setTreeComparator();
        accountTreeRoot.setComparator(treeComparator);
        refreshTreeGroups();
        for(Account acc : accounts) {
            if(!acc.getIsDeleted()) {
                addNodeToGroup(acc, accountTreeRoot, acc.getGroup());
            }
        }
        accountTree.repaint();
        defAccountTreeModel.reload();
        expandNodes(accountTree, new TreePath(accountTreeRoot));
    }

    /**
     * Automatically sends the configured keystroke configuration for the selected account if AutoType is configured
     */
    private void doAutoType(Account account) {
        if(autoTyper != null) {
            if(account != null && account.fieldExists("AutoType")) {
                try {
                    JSONObject jsoAutoTypeConfig = new JSONObject(account.getField("AutoType"));
                    String config = jsoAutoTypeConfig.getString("config");
                    config = config.replace("\\%", "{%}");
                    config = config.replace("\\^", "{^}");
                    config = config.replace("\\+", "{+}");
                    config = config.replace("\\~", "{~}");
                    config = config.replace("\\(", "{(}");
                    config = config.replace("\\)", "{)}");
                    config = config.replace("\\{", "{{}");
                    config = config.replace("\\}", "{}}");
                    config = config.replace("\\\\", "\\");
                    
                    Pattern regex = Pattern.compile("\\$.[^\\$]+\\$");
                    Matcher fieldMatcher = regex.matcher(config);
                    int end = 0;
                    StringBuffer parsedConfig = new StringBuffer();
                    String fieldName;
                    while(fieldMatcher.find()) {
                        parsedConfig.append(config.substring(end, fieldMatcher.start()));
                        end = fieldMatcher.end();
                        fieldName = config.substring(fieldMatcher.start()+1, fieldMatcher.end()-1);
                        if(account.fieldExists(fieldName)) {
                            parsedConfig.append(account.getField(fieldName));
                        } else {
                            parsedConfig.append(fieldName);
                        }
                    }
                    if(end < config.length()) {
                        parsedConfig.append(config.substring(end));
                    }
                    autoTyper.doAutoType(jsoAutoTypeConfig.getString("target"), parsedConfig.toString());
                } catch(Exception ex) {
                    JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+" #224: "+ex.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                }
            } else {
                JOptionPane.showMessageDialog(mainWindow, lang.getString("ERR_AUTOTYPENOTCONFIGURED"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
            }
        }
    }
    
    /**
     * Registers the hotkey for AutoType
     */
    private void registerAutoTypeHotkey() {
        try {
            int modifiers = 0;
            if(setAutoTypeHotkey.indexOf("ctrl") >= 0) {
                modifiers |= KeyEvent.CTRL_DOWN_MASK;
            }
            if(setAutoTypeHotkey.indexOf("alt") >= 0) {
                modifiers |= KeyEvent.ALT_DOWN_MASK;
            }
            if(setAutoTypeHotkey.indexOf("shift") >= 0) {
                modifiers |= KeyEvent.SHIFT_DOWN_MASK;
            }
            char shortcutChar = setAutoTypeHotkey.length() == 1 ? setAutoTypeHotkey.charAt(0) : setAutoTypeHotkey.charAt(setAutoTypeHotkey.length()-1);
            //KeyStroke autofillKeystroke = KeyStroke.getKeyStroke(KeyStroke.getKeyStroke(shortcutChar).getKeyCode(), modifiers, true);
            KeyStroke autofillKeystroke = KeyStroke.getKeyStroke(shortcutChar, modifiers);
            Action autofillAction = new AbstractAction() {
                public void actionPerformed(ActionEvent e) { doAutoType(selectedAccount); }
            };
            this.getRootPane().getActionMap().put("AUTOTYPE", autofillAction);
            this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(autofillKeystroke, "AUTOTYPE");
        } catch(Exception ex) {
            JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+" #226: "+ex.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /** <b>Recursively</b> expands all nodes in the specified JTree.
     * @param tree The tree to expand
     * @param parent The TreePath of the root node of the tree. Needed to make the method recursive.
     */
    private void expandNodes(JTree tree, TreePath parent) {
        TreeNode node = (TreeNode)parent.getLastPathComponent();
        if(node.getChildCount() >= 0) {
            for(Enumeration e=node.children(); e.hasMoreElements(); ) {
                TreeNode thisNode = (TreeNode)e.nextElement();
                TreePath path = parent.pathByAddingChild(thisNode);
                expandNodes(tree, path);
            }
        }
        tree.expandPath(parent);
    }
    
    /** Creates a JPopupMenu for the Account Tree, filling it with items to edit/delete the currently selected
     * account or group or add subgroups.
     * @return A JPopupMenu with items related to the context of the click.
     */
    private JPopupMenu createPopupMenu() {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)accountTree.getLastSelectedPathComponent();
        if(node == null) {
            return null; //nothing was selected
        } else {
            Icon delIcon = null, editIcon = null, addIcon = null, createCopyIcon = null, autoTypeIcon = null;
            try {
                addIcon = new ImageIcon(this.getClass().getResource("images/add_16.png"), lang.getString("ADD"));
                delIcon = new ImageIcon(this.getClass().getResource("images/delete_16.png"), lang.getString("DELETE"));
                editIcon = new ImageIcon(this.getClass().getResource("images/edit_16.png"), lang.getString("EDIT"));
                createCopyIcon = new ImageIcon(this.getClass().getResource("images/copy_16.png"), lang.getString("CREATECOPY"));
                autoTypeIcon = new ImageIcon(this.getClass().getResource("images/autotype_16.png"), lang.getString("AUTOTYPE"));
            } catch(Exception ignore) {}
            if(node instanceof Account) {
                accountTreePopup = new JPopupMenu();
                JMenuItem itmCtxAutoType = new JMenuItem(lang.getString("AUTOTYPE"), autoTypeIcon);
                    accountTreePopup.add(itmCtxAutoType);
                    itmCtxAutoType.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { doAutoType(selectedAccount); }
                    });
                JMenuItem itmCtxCreateCopy = new JMenuItem(lang.getString("CREATECOPY"), createCopyIcon);
                    accountTreePopup.add(itmCtxCreateCopy);
                    itmCtxCreateCopy.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { createCopy(selectedAccount); }
                    });
                JMenuItem itmEditAccount = new JMenuItem(lang.getString("EDITACCOUNT"), editIcon);
                    accountTreePopup.add(itmEditAccount);
                    itmEditAccount.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { showAddDialog(selectedAccount, true); } 
                    });
                JMenuItem itmRemoveAccount = new JMenuItem(lang.getString("DELACCOUNT"), delIcon);
                    accountTreePopup.add(itmRemoveAccount);
                    itmRemoveAccount.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { removeAccount(selectedAccount); } 
                    });
            } else if(node.isRoot() || node instanceof Group) {
                accountTreePopup = new JPopupMenu();
                JMenuItem itmAddSubGroup = new JMenuItem(lang.getString("ADDSUBGROUP"), addIcon);
                    accountTreePopup.add(itmAddSubGroup);
                    itmAddSubGroup.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { 
                           Group parentGroup = selectedGroup;
                           Group newGroup = new Group();
                           newGroup.setParentName(parentGroup.getName());
                           showAddGroupDialog(newGroup, false);
                       } 
                    });
                JMenuItem itmAddAccount = new JMenuItem(lang.getString("ADDACCOUNT"), addIcon);
                    accountTreePopup.add(itmAddAccount);
                    itmAddAccount.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { 
                           Group parentGroup = selectedGroup;
                           Account newAccount = new Account();
                           newAccount.setGroup(parentGroup.getName());
                           showAddDialog(newAccount, false); 
                       } 
                    });
                JMenuItem itmRenameGroup = new JMenuItem(lang.getString("RENAME"), editIcon);
                    accountTreePopup.add(itmRenameGroup);
                    itmRenameGroup.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { showAddGroupDialog(selectedGroup, true); } 
                    });
                JMenuItem itmRemoveGroup = new JMenuItem(lang.getString("DELGROUP"), delIcon);
                    accountTreePopup.add(itmRemoveGroup);
                    itmRemoveGroup.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) { 
                           removeGroup(selectedGroup,true);
                       } 
                    });
                if(node.isRoot()) {
                    itmRenameGroup.setEnabled(false);
                    itmRemoveGroup.setEnabled(false);
                } else {
                    itmRenameGroup.setEnabled(true);
                    itmRemoveGroup.setEnabled(true);
                }
            }
        }
            
        return accountTreePopup;
    }
    
    /** Creates a toolbar
     * @return The toolbar
     */
    private JToolBar createToolbar() {
        toolBar = new JToolBar();
        toolBar.putClientProperty(PlasticLookAndFeel.IS_3D_KEY, Boolean.TRUE); // Custom LookAndFeel
        toolBar.setFloatable(false);
        final JPopupMenu addMenu = new JPopupMenu();
            MouseMenuItem itmAddAccountPopup = new MouseMenuItem(lang.getString("ADDACCOUNT"));
                addMenu.add(itmAddAccountPopup);
                itmAddAccountPopup.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) { 
                       addMenu.setVisible(false);
                       showAddDialog(new Account(), false);
                   } 
                });
            MouseMenuItem itmAddGroupPopup = new MouseMenuItem(lang.getString("ADDGROUP"));
                addMenu.add(itmAddGroupPopup);
                itmAddGroupPopup.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) {
                       addMenu.setVisible(false);
                       showAddGroupDialog(new Group(), false);
                   } 
                });
        btnAdd = createDropDownButton(lang.getString("ADD"), "add_32.png", "add",'A', addMenu);
            btnAdd.getMenu().setInvoker(this);
            toolBar.add(btnAdd);
        btnRemove = createButton(lang.getString("DELETE"), "delete_32.png", "remove",'D');
            toolBar.add(btnRemove);
        btnEdit = createButton(lang.getString("EDIT"), "edit_32.png", "edit",'E');
            toolBar.add(btnEdit);
        toolBar.addSeparator();
        btnSearch = createButton(lang.getString("SEARCH"), "search_32.png", "search",'S');
            toolBar.add(btnSearch);
        btnPrint = createButton(lang.getString("PRINT"), "print_32.png", "print",'P');
            toolBar.add(btnPrint);
        toolBar.add(createButton(lang.getString("OPTIONS"), "settings_32.png", "settings",'O'));
        btnRelock = createButton(lang.getString("RELOCK"), "lock_32.png", "relock",'R');
            toolBar.add(btnRelock);
        JButton btnPWGen = createButton(lang.getString("PWGEN"), "password_32.png", "pwgen",'P');
            toolBar.add(btnPWGen);
        Icon view_largeIcon = null, view_smallIcon = null, view_listIcon = null, view_tilesIcon = null;
        try {
            URL view_largeURL = this.getClass().getResource("images/icons_large_16.png");
            view_largeIcon = new ImageIcon(view_largeURL, lang.getString("VIEW_LARGEICONS"));
            URL view_smallURL = this.getClass().getResource("images/icons_small_16.png");
            view_smallIcon = new ImageIcon(view_smallURL, lang.getString("VIEW_SMALLICONS"));
            URL view_listURL = this.getClass().getResource("images/icons_list_16.png");
            view_listIcon = new ImageIcon(view_listURL, lang.getString("VIEW_LIST"));
            URL view_tilesURL = this.getClass().getResource("images/icons_tiles_16.png");
            view_tilesIcon = new ImageIcon(view_tilesURL, lang.getString("VIEW_TILES"));
        } catch(Exception ignore) {/**/}
        btnViews = createButton(lang.getString("MNU_VIEW"), "view_32.png", "view",'V');
            toolBar.add(btnViews);
        btnSynchronize = createButton(lang.getString("SYNCHRONIZE"), "sync_32.png", "synchronize",'Y');
            toolBar.add(btnSynchronize);
        return toolBar;
    }
    
    /** Creates a JButton with the specified attributes.
     * @param alttext The tooltip text to display
     * @param image The image to be used
     * @param actioncommand The ActionCommand to give this button
     * @param mnemonic The mnemonic to use
     * @return The completed JButton
     */
    private JButton createButton(String alttext, String image, String actionCommand, char mnemonic) {
        JButton button = new JButton();
        button.setActionCommand(actionCommand);
        button.addActionListener(toolBarListener);
        button.setToolTipText(alttext);
        try {
            URL iconURL = this.getClass().getResource("images/"+image);
            if(iconURL != null) {
                Icon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                button.setIcon(imgIcon);
            }
        } catch(Exception ignore) {/**/}
        button.setText(alttext);
        button.setVerticalTextPosition(SwingConstants.BOTTOM);
        button.setHorizontalTextPosition(SwingConstants.CENTER);
        button.setMnemonic(mnemonic);
        return button;
    }
    
    /** Creates a DropDownButton with the specified attributes.
     * @param alttext The tooltip text to display
     * @param image The image to be used
     * @param actioncommand The ActionCommand to give this button
     * @param mnemonic The mnemonic to use
     * @param menu The menu to show when the arrow is clicked
     * @return The completed DropDownButton
     */
    private DropDownButton createDropDownButton(String alttext, String image, String actionCommand, char mnemonic, JPopupMenu menu) {
        JButton button = new JButton();
        button.setActionCommand(actionCommand);
        button.addActionListener(toolBarListener);
        button.setToolTipText(alttext);
        try {
            URL iconURL = this.getClass().getResource("images/"+image);
            if(iconURL != null) {
                Icon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                button.setIcon(imgIcon);
            }
        } catch(Exception ignore) {/**/}
        button.setText(alttext);
        button.setVerticalTextPosition(SwingConstants.BOTTOM);
        button.setHorizontalTextPosition(SwingConstants.CENTER);
        button.setMnemonic(mnemonic);
        return new DropDownButton(button, SwingConstants.SOUTH, menu);
    }
    
    /**
     * Create a copy of an account
     * @param account The account to copy
     */
    private void createCopy(Account account) {
        MessageFormat singleFormatter = new MessageFormat(lang.getString("COPYFORMAT"));
        MessageFormat multipleFormatter = new MessageFormat(lang.getString("COPYFORMATMULTIPLE"));
        String newName = "";
        boolean foundName = false;
        int copyNumber = 1;
        while(!foundName) {
            if(copyNumber == 1) {
                newName = singleFormatter.format(new Object[] {account.getName()});
            } else {
                newName = multipleFormatter.format(new Object[] {copyNumber, account.getName()});
            }
            if(Utils.getTreeIndex(newName, accountTree) < 0) {
                foundName = true;
            }
            copyNumber++;
        }
        Account newAccount = (Account)account.clone();
        newAccount.setName(newName);
        addAccount(newAccount);
    }
    
    /** Sets the toolbar buttons to reflect the selected account or group
     * @param state The settings to use<br>
     *        <b>1</b> to disable the buttons
     *        <b>2</b> for an account
     *        <b>3</b> for a group
     */
    public void setMenuButtons(int state) {
        // 0 - Disabled, 1 - account, 2- group
        switch(state) {
            case 0:
                itmCreateCopy.setVisible(false);
                itmEdit.setEnabled(false);
                itmEdit.setText(lang.getString("EDIT"));
                itmRemove.setEnabled(false);
                itmRemove.setText(lang.getString("DELETE"));
                btnEdit.setEnabled(false);
                btnEdit.setToolTipText(lang.getString("EDIT"));
                btnRemove.setEnabled(false);
                btnRemove.setToolTipText(lang.getString("DELETE"));
                itmAutoType.setEnabled(false);
                break;
            case 1:
                itmCreateCopy.setVisible(true);
                itmEdit.setEnabled(true);
                itmEdit.setText(lang.getString("EDITACCOUNT"));
                itmRemove.setEnabled(true);
                itmRemove.setText(lang.getString("DELETE"));
                btnEdit.setEnabled(true);
                btnEdit.setToolTipText(lang.getString("EDIT"));
                btnRemove.setEnabled(true);
                btnRemove.setToolTipText(lang.getString("DELETE"));
                itmAutoType.setEnabled(true);
                break;
            case 2:
                itmCreateCopy.setVisible(false);
                itmEdit.setEnabled(true);
                itmEdit.setText(lang.getString("EDIT"));
                itmRemove.setEnabled(true);
                itmRemove.setText(lang.getString("DELGROUP"));
                btnEdit.setEnabled(true);
                btnEdit.setToolTipText(lang.getString("EDITGROUP"));
                btnRemove.setEnabled(true);
                btnRemove.setToolTipText(lang.getString("DELETE"));
                itmAutoType.setEnabled(false);
                break;
        }
    }
    
    /** Shows the file import dialog.
     * @param type The type to select when the dialog is shown
     */
    private void showImportDialog(FrmImportExport.DataType type) {
        FrmImportExport frmImportExport = new FrmImportExport(this, type, FrmImportExport.DataDirection.Import, accounts, types);
        if(frmImportExport.getResult() == JOptionPane.OK_OPTION) {
            setLastExportLocation = prefs.get("setlastexportlocation", System.getenv("USERPROFILE")+File.separatorChar+"LockCrypt.xml");
        }  
    }

    /** Shows the file export dialog.
     * @param type The type to select when the dialog is shown
     */
    private void showExportDialog(FrmImportExport.DataType type) {
        FrmImportExport frmImportExport = new FrmImportExport(this, type, FrmImportExport.DataDirection.Export, accounts, types);
        if(frmImportExport.getResult() == JOptionPane.OK_OPTION) {
            setLastExportLocation = prefs.get("setlastexportlocation", System.getenv("USERPROFILE")+File.separatorChar+"LockCrypt.xml");
        }
    }
    
    /** Sets the selected account by selecting it in the account tree, then calling showAccount()
     * @param account The account to select
     */
    public void setSelectedAccount(Account account) {
        selectedAccount = account;
        TreePath path = getPathToNode(accountTreeRoot, account);
        accountTree.scrollPathToVisible(path);
        if((accountTree.getSelectionPath() == null) || (!accountTree.getSelectionPath().equals(path))) {
            accountTree.setSelectionPath(path);
        }
        showAccount(selectedAccount);
    }
    
    /** Sets the selected group by selecting it in the account tree, then calling showGroup()
     * @param group The group to select
     */
    private void setSelectedGroup(Group group) {
        selectedGroup = group;
        TreePath path = getPathToNode(accountTreeRoot, group);
        accountTree.scrollPathToVisible(path);
        if((accountTree.getSelectionPath() == null) || (!accountTree.getSelectionPath().equals(path))) {
            accountTree.setSelectionPath(path);
        }
        showGroup(group);
    }
    
    /** Returns a TreePath to the specified node by recursively searching the tree. Used to scroll the JTree to the current account.
     * @param node The starting node (should always be the tree root).
     * @param nodeToFind The object to find
     * @return A TreePath to the node, or null if it was not found
     */
    private TreePath getPathToNode(DefaultMutableTreeNode node, Object nodeToFind) {
        if(node.toString().equals(nodeToFind.toString())) {
            return new TreePath(node.getPath());
        } else {
            for(Enumeration theChildren = node.children(); theChildren.hasMoreElements();) {
                DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode)theChildren.nextElement();
                if(thisNode.toString().equals(nodeToFind.toString())) {
                    return new TreePath(thisNode.getPath());
                } else if(thisNode.getChildCount()>0) {
                    for(Enumeration grandChildren = thisNode.children(); grandChildren.hasMoreElements();) {
                        DefaultMutableTreeNode grandChild = (DefaultMutableTreeNode)grandChildren.nextElement();
                        TreePath n = getPathToNode(grandChild, nodeToFind);
                        if(n != null) {
                            return n; // if the call returns anything but null, we've found the node
                        }
                    }
                }
            }
            return null;
        }
    }
    
    /*============================ ILanguageParent ==========================*/
    /**
     * Returns the current langiage ResourceBundle
     * @return The current langiage ResourceBundle
     */
    public ResourceBundle getLanguage() {
        return this.lang;
    }
    
    public Component getParentWindow() {
        return mainWindow;
    }
    /*============================ End ILanguageParent ==========================*/

    public Preferences getPreferences() {
        return prefs;
    }
    
    /** Searches the tree to find a node with the specified name.
     * @param node The starting node (should always be the tree root).
     * @param nodeToFind The object to find
     * @return A DefaultMutableTreeNode which contains the object
     */
    private AccountTreeNode getNodeFromName(AccountTreeNode node, Object nodeToFind) {
        // Used to scroll the JTree to the current account
        if(node.toString().equals(nodeToFind.toString())) {
                return node;
        }
        for(Enumeration theChildren = node.children(); theChildren.hasMoreElements();) {
            AccountTreeNode thisNode = (AccountTreeNode)theChildren.nextElement();
            if(thisNode.toString().equals(nodeToFind.toString())) {
                return thisNode;
            } else {
                if(thisNode.getChildCount()>0) {
                    for(Enumeration grandChildren = thisNode.children(); grandChildren.hasMoreElements();) {
                        AccountTreeNode grandChild = (AccountTreeNode)grandChildren.nextElement();
                        AccountTreeNode n = getNodeFromName(grandChild, nodeToFind);
                        if(n != null) {
                            return n; // if the call returns anything but null, we've found the node
                        }
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * Recursively searches the specified node and all child nodes by calling itself if a child node has children.
     * @param node The node from which to return children.
     * @param childrenParentNodes An ArrayList of children found so far. Needed to recursively process nodes.
     * @return An ArrayList of Account objects contained at the starting node, <i>node</i>.
     */
    private ArrayList<Account> getChildrenThisNode(DefaultMutableTreeNode node, ArrayList<Account>childrenParentNodes) {
        ArrayList<Account> childrenThisNode = childrenParentNodes;
        for(Enumeration theChildren = node.children(); theChildren.hasMoreElements();) {
            DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode)theChildren.nextElement();
            if(thisNode.getChildCount()>0) {
                childrenThisNode.addAll(getChildrenThisNode(thisNode, new ArrayList<Account>()));
            } else {
                /*if(thisNode.getUserObject() instanceof Account) {
                    childrenThisNode.add((Account)thisNode.getUserObject());
                }*/
                if(thisNode instanceof Account) {
                    childrenThisNode.add((Account)thisNode);
                }
            }
        }
        return childrenThisNode;
    }
    
    /**
     * Shows the accounts present in the specified group and all of it's subgroups by recursively enumerating the 
     * children in the group using <i>getChildrenThisNode()</i>. The choldren are shown in the center component of mainContent.
     * @param group The group to be shown.
     */
    private void showGroup(Group group) {
        JPanel groupContainer = new JPanel();
            groupContainer.setBackground(Color.WHITE);
            groupContainer.setLayout(new BoxLayout(groupContainer,BoxLayout.PAGE_AXIS));
        accountView.removeAll(); // remove the old group/account info
        String groupName = group.getName();
        JPanel pnlHeader = new JPanel(new FlowLayout(FlowLayout.LEADING));
            pnlHeader.setBackground(Color.getHSBColor(0.58f, 0.1f, 0.95f));
            pnlHeader.setMinimumSize(new Dimension(accountView.getSize().width-accountTree.getWidth(),68));
            pnlHeader.setMaximumSize(new Dimension(1000,68));
            groupContainer.add(pnlHeader);
            JLabel lblName = new JLabel("<HTML><H1>"+groupName+"</H1></HTML>");
                lblName.setHorizontalTextPosition(JLabel.RIGHT);
            ImageIcon icoName = new ImageIcon();
            try {
                URL URLicon = this.getClass().getResource("images/types/folder_open_64.png");
                if(URLicon == null) { // no 64x64px image was found, so try 32x32
                    URLicon = this.getClass().getResource("images/types/folder_open_32.png");
                    icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                } else {
                    icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                }
                lblName.setIcon(icoName);
            } catch(Exception e) {

            }
            pnlHeader.add(lblName);
        DefaultMutableTreeNode thisGroupNode = getNodeFromName(accountTreeRoot, group.getName());
        if(!thisGroupNode.isLeaf() && thisGroupNode.getChildCount()>0) {
            ArrayList<Account> accountsThisNode = getChildrenThisNode(thisGroupNode, new ArrayList<Account>());
            groupTable = createIconTable(accountsThisNode, accountView);
            groupTable.addMouseListener(new GroupViewListener());
            groupTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            //dragSource = DragSource.getDefaultDragSource();
            //DragGestureRecognizer dragGest = dragSource.createDefaultDragGestureRecognizer(groupTable, DnDConstants.ACTION_MOVE, new TableDragListener());
                                                                                         //dragSource,                  DragGestureListener
            //dragGest.setSourceActions(dragGest.getSourceActions() &~InputEvent.BUTTON3_MASK); // ignores right clicks which could screw with the popupmenu
            JScrollPane groupScroll = new JScrollPane(groupTable);
                groupContainer.add(groupScroll);
                groupScroll.getVerticalScrollBar().setUnitIncrement(15); // increase the default scrolling speed
        }
            groupContainer.setPreferredSize(new Dimension(accountView.getSize().width+15, accountView.getSize().height+15));
            groupContainer.setMaximumSize(new Dimension(2000,2000));
        accountView.add(groupContainer);
        
        accountView.revalidate();
        accountView.repaint();
    }
    
    /**
     * Shows the 'About LockCrypt' dialog.
     */
    private void showAbout() {
        FrmAbout frmAbout = new FrmAbout(this, lang);
        frmAbout.setVisible(true);
    }
    
    /**
     * Shows an account in the center component of mainContent.
     * @param account The account to be shown.
     */
    private void showAccount(Account account) {
        accountView.removeAll(); // remove the old account info
        JPanel pnlHeader = new JPanel(new FlowLayout(FlowLayout.LEADING));
            pnlHeader.setBackground(Color.getHSBColor(0.58f, 0.1f, 0.95f));
            accountView.add(pnlHeader, BorderLayout.NORTH);
            JLabel lblName = new JLabel("<HTML><H1>"+account.getName()+"</H1></HTML>");
                lblName.setHorizontalTextPosition(JLabel.RIGHT);
            ImageIcon icoName = new ImageIcon();
            try {
                URL URLicon = this.getClass().getResource("images/types/"+account.getIcon()+"_64.png");
                if(URLicon == null) { // no 64x64px image was found, so try 32x32
                    URLicon = this.getClass().getResource("images/types/"+account.getIcon()+"_32.png");
                    if(URLicon == null) { // no 32x32px image was found, use the default
                        URLicon = this.getClass().getResource("images/types/default_64.png");
                    }
                    icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                } else {
                    icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                }
                lblName.setIcon(icoName);
            } catch(Exception ignore) { /**/ }
            pnlHeader.add(lblName);
        JPanel pnlAccount = new JPanel(new GridBagLayout());
            pnlAccount.setBackground(Color.WHITE);
            pnlAccount.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
        JScrollPane scrollAccount = new JScrollPane(pnlAccount, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            accountView.add(scrollAccount, BorderLayout.CENTER);
        GridBagConstraints av = new GridBagConstraints();
            int gridy = 0;
        Icon copyIcon = null, 
             copyIcon_active = null,
             peekIcon = null,
             urlIcon = null;
        try {
            URL copyIconURL = this.getClass().getResource("images/copy_16.png");
            copyIcon = new ImageIcon(copyIconURL, lang.getString("COPYTOCLIP"));
            URL copyIcon_activeURL = this.getClass().getResource("images/copy_active_16.png");
            copyIcon_active = new ImageIcon(copyIcon_activeURL, lang.getString("COPYTOCLIP"));
        } catch(Exception ignore) {/**/}
        try {
            URL peekIconURL = this.getClass().getResource("images/peek.png");
            peekIcon = new ImageIcon(peekIconURL, lang.getString("REVEALPASSWORD"));
        } catch(Exception ignore) {/**/}
        try {
            URL urlIconURL = this.getClass().getResource("images/language_16.png");
            urlIcon = new ImageIcon(urlIconURL, lang.getString("OPENURL"));
        } catch(Exception ignore) {/**/}
        av.insets = new Insets(3,3,3,3);
        CopyListener copyListener = new CopyListener();
        PeekListener peekListener = new PeekListener();
        URLListener urlListener = new URLListener();

        for(Object fieldname : account.info.getFieldMap().keySet()) {
            String fieldName = fieldname.toString(),
                   prettyFieldName = null,
                   thisValue;
            boolean obfuscated = false,
                    url = false;
            if(fieldName.startsWith("~}")) {
                switch(fieldName.charAt(2)) {
                    case '*':
                        prettyFieldName = fieldName.substring(3);
                        obfuscated = true;
                        break;
                    case '#':
                        prettyFieldName = fieldName.substring(3);
                        break;
                    case '!':
                        continue;
                    case 'u':
                        prettyFieldName = fieldName.substring(3);
                        url = true;
                        break;
                    default:
                        prettyFieldName = fieldName;
                        break;
                }
            } else {
                prettyFieldName = fieldName;
            }
            thisValue = (String)account.info.getFieldMap().get(fieldName);
            if(thisValue.length() == 0) {
                continue;
            }
            JLabel lblFieldname = new JLabel(prettyFieldName);
                lblFieldname.setFont(lblFieldname.getFont().deriveFont(Font.BOLD));
            JTextPane lblFieldval = new JTextPane();
                lblFieldval.setBackground(null);
                lblFieldval.setEditable(false);
                lblFieldval.setMargin(null);
                if(obfuscated) {
                    lblFieldval.setText("******");
                } else {
                    lblFieldval.setText(thisValue);
                }
                lblFieldname.setLabelFor(lblFieldval);
            JButton btnCopy = new JButton(copyIcon);
                btnCopy.setBorder(null);
                btnCopy.setBorderPainted(false);
                btnCopy.setBackground(null);
                btnCopy.setPressedIcon(copyIcon_active);
                btnCopy.setActionCommand(thisValue);
                btnCopy.addActionListener(copyListener);
                btnCopy.setSize(20,20);
            Utils.setgrid(av,gridy,0,1,1,GridBagConstraints.NONE,GridBagConstraints.FIRST_LINE_END,0.4);
                pnlAccount.add(lblFieldname,av);
            Utils.setgrid(av,gridy,1,1,1,GridBagConstraints.NONE,GridBagConstraints.PAGE_START,0.1);
                pnlAccount.add(btnCopy,av);
                
            if(obfuscated) {
                JButton btnPeek = new JButton(peekIcon);
                    btnPeek.setBorder(null);
                    btnPeek.setBorderPainted(false);
                    btnPeek.setBackground(null);
                    btnPeek.setActionCommand(thisValue);
                    btnPeek.addActionListener(peekListener);
                    btnPeek.setSize(20,20);
                Utils.setgrid(av,gridy,2,1,1,GridBagConstraints.HORIZONTAL,GridBagConstraints.FIRST_LINE_START,0.9);
                    pnlAccount.add(lblFieldval,av);
                Utils.setgrid(av,gridy,3,1,1,GridBagConstraints.NONE,GridBagConstraints.FIRST_LINE_START,0.1);
                    pnlAccount.add(btnPeek,av);
            } else if(url) {
                JButton btnOpenUrl = new JButton(urlIcon);
                    btnOpenUrl.setBorder(null);
                    btnOpenUrl.setBorderPainted(false);
                    btnOpenUrl.setBackground(null);
                    btnOpenUrl.setActionCommand(thisValue);
                    btnOpenUrl.addActionListener(urlListener);
                    btnOpenUrl.setSize(20,20);
                Utils.setgrid(av,gridy,2,1,1,GridBagConstraints.HORIZONTAL,GridBagConstraints.FIRST_LINE_START,0.9);
                    pnlAccount.add(lblFieldval,av);
                lblFieldval.setForeground(Color.blue);
                lblFieldval.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                Utils.setgrid(av,gridy,3,1,1,GridBagConstraints.NONE,GridBagConstraints.FIRST_LINE_START,0.1);
                    pnlAccount.add(btnOpenUrl,av);
                    final String fVal = thisValue;
                SimpleAttributeSet attr = new SimpleAttributeSet();
                StyleConstants.setUnderline(attr, true);
                lblFieldval.getStyledDocument().setCharacterAttributes(0, thisValue.length(), attr, true);
                lblFieldval.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        Utils.openURL(fVal);
                    }
                });
            } else {
                Utils.setgrid(av,gridy,2,2,1,GridBagConstraints.HORIZONTAL,GridBagConstraints.FIRST_LINE_START,1);
                    pnlAccount.add(lblFieldval,av);
            }
            gridy++;
        }
        if(account.getShowNotes() && account.getNotes().length() > 0) {
            JLabel lblNotesLabel = new JLabel(lang.getString(("NOTES")));
                lblNotesLabel.setFont(lblNotesLabel.getFont().deriveFont(Font.BOLD));
            JTextPane lblNotes = new JTextPane();
                lblNotes.setBackground(Color.white);
                lblNotes.setEditable(false);
                lblNotes.setMargin(null);
                lblNotes.setText(account.getNotes());
                lblNotesLabel.setLabelFor(lblNotes);
            JButton btnCopy = new JButton(copyIcon);
                btnCopy.setBorder(null);
                btnCopy.setBackground(null);
                btnCopy.setPressedIcon(copyIcon_active);
                btnCopy.setActionCommand(account.getNotes());
                btnCopy.addActionListener(copyListener);
                btnCopy.setSize(20,20);
            Utils.setgrid(av,gridy,0,1,1,GridBagConstraints.NONE,GridBagConstraints.FIRST_LINE_END,0.4);
                pnlAccount.add(lblNotesLabel,av);
            Utils.setgrid(av,gridy,1,1,1,GridBagConstraints.NONE,GridBagConstraints.PAGE_START,0.1);
                pnlAccount.add(btnCopy,av);
            Utils.setgrid(av,gridy,2,2,1,GridBagConstraints.HORIZONTAL,GridBagConstraints.FIRST_LINE_START,1);
            JScrollPane scrollNotes = new JScrollPane(lblNotes);
                    scrollNotes.setBorder(null);
                scrollNotes.setPreferredSize(new Dimension(300,100));
                pnlAccount.add(scrollNotes,av);
                gridy++;
        }
        av.weighty = 1;
        av.fill = GridBagConstraints.BOTH;
        pnlAccount.add(Box.createVerticalGlue(), av);
        accountView.revalidate();
        accountView.repaint();
    }
    
    /**
     * Called by showWelcome() when the Finish button is clicked, saves the settings entered.
     * Enables the menu buttons and adds the account tree to the layout.
     */
    private void processWelcome(FrmWelcomeWizard wizard) {
            String[] data = wizard.getSetup();
            setJCEKey = data[0];
            setLang = data[8];
            lang = Utils.getLanguage(setLang);
            if(data[1].equals(lang.getString("TXTFILE"))) {
                setDBMode = "text";
            } else if(data[1].equals(lang.getString("MYSQL"))) {
                setDBMode = "mysql";
            }
            setFileName = data[2];
            setDBHost = data[3];
            setDBUser = data[4];
            setDBPass = data[5];
            setDBName = data[6];
            prefs.put("setdbmode", setDBMode);
            prefs.put("setfilename", setFileName);
            prefs.put("setdbhost", setDBHost);
            prefs.put("setdbuser", setDBUser);
            prefs.put("setdbpass", setDBPass);
            prefs.put("setdbname", setDBName);
            prefs.put("setlang", setLang);
            prefs.putBoolean("setsecureclipboard", true);
            prefs.putBoolean("setfirstload", false);
            prefs.putInt("setsortmode", (int)setSortMode);
            prefs.putInt("setviewmode", (int)setViewMode);
            prefs.putInt("setsecureclipdelay", 10);
            prefs.put("setautotypehotkey", "ctrl+shift+T");
            saveDatabase(null,false, true);
            setMenuItems(true);
            splitPane.setLeftComponent(accountTreeScroll);
            splitPane.setDividerLocation(prefs.getInt("setsplitlocation", this.getWidth()/3));
            maincontent.revalidate();
            resetDefaultTypes();
            if(data[7].equals("1")) {
                resetDefaultAccounts();
            } else {
                addWebsiteType();
            }
            createMenuToolBars();
            wizard.dispose();
            setSelectedGroup((Group)accountTreeRoot);
            updateLookNFeel();
    }
    
    /**
     * Shown on the first load after of LockCrypt. Prompts the user to create 
     * a password to use for encrypting the database.
     */
    private void showWelcome() {
        JPanel pnlHeader = new JPanel(new FlowLayout(FlowLayout.LEADING));
            pnlHeader.setBackground(Color.getHSBColor(0.58f, 0.1f, 0.95f));
            accountView.add(pnlHeader,BorderLayout.PAGE_START);
            JLabel lblName = new JLabel("<HTML><H1>LockCrypt</H1></HTML>");
                lblName.setHorizontalTextPosition(JLabel.RIGHT);
            try {
                ImageIcon icoName = new ImageIcon();
                URL URLicon = this.getClass().getResource("images/icon_64.png");
                icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                lblName.setIcon(icoName);
            } catch(Exception e) { }
            pnlHeader.add(lblName);
            
        FrmWelcomeWizard wizard = new FrmWelcomeWizard(this);
        if(wizard.getResult() == JOptionPane.OK_OPTION) {
            processWelcome(wizard);
        } else {
            System.exit(0);
        }
    }
    
    /**
     * Starts the synchronization utility.
     */
    private void launchSyncUtility() {
        if(System.getProperty("os.name").toLowerCase().startsWith("windows")) {
            String jarLocation = System.getProperty("user.dir");
            String exeLocation = jarLocation.endsWith("/") ? jarLocation+"sync/LockCrypt_Sync.exe" : jarLocation+"/sync/LockCrypt_Sync.exe";
            try {
                Runtime.getRuntime().exec(exeLocation);
            } catch(Exception e) {
                JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+" #216: "+lang.getString("SyncLaunchError")+": "+exeLocation+"\n"+e.getMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(mainWindow, getLanguage().getString("ERR_SYNCNOTWINDOWS"), getLanguage().getString("INFORMATION"), JOptionPane.INFORMATION_MESSAGE);
        }
    }
    
    /**
     * Creates a label containing a question mark which shows a tooltip on mouseover.
     * @param toolTip The text to show when the mouse is hovered over the help icon.
     * @return A JLabel with the specified tooltip text.
     */
    public JLabel createHelpIcon(final String toolTip) {
        JLabel helpLabel = new JLabel();
        try {
            URL iconURL = this.getClass().getResource("images/help.png");
            if(iconURL == null) {
                if(toolTip.equals("spacere")) {
                    helpLabel.setText(" ");
                } else {
                    helpLabel.setText("?");
                }
            } else {
                Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL),toolTip);
                if(toolTip.equals("spacer")) {
                    helpLabel.setSize(icon.getIconWidth(), icon.getIconHeight());
                } else {
                    helpLabel.setIcon(icon);
                }
            }
        } catch(Exception e) {
            helpLabel.setText("?");
        }
        helpLabel.setToolTipText(toolTip);
        return helpLabel;
    }
    
    /**
     * Creates an instance of the Print class, then passes is an array of the currently showing accounts.
     */
    private void showPrintDialog() {
        DefaultMutableTreeNode thisNode = accountTree.getSelectedNode();
        ArrayList<Account> accountsToPrint = getChildrenThisNode(thisNode, new ArrayList<Account>());
        if(accountsToPrint.size() > 0) {
            Print printer = new Print(accountsToPrint, lang);
                printer.showDialog();
        } else {
            JOptionPane.showMessageDialog(null, lang.getString("ERR_NOACCOUNTSFOUND"), lang.getString("WARNING"), JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * Creates and shows a dialog which prompts the user for a String to search for.
     */
    private void showSearchDialog() {
        searchDialog = new JFrame(lang.getString("SEARCH"));
            searchDialog.setIconImage(this.getIconImage());
        KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
        Action escapeAction = new AbstractAction() {
            // close the frame when the user presses escape
            public void actionPerformed(ActionEvent e) {
                searchDialog.dispose();
            }
        }; 
        searchDialog.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
        searchDialog.getRootPane().getActionMap().put("ESCAPE", escapeAction);
        searchContainer = new JPanel(new BorderLayout());
            searchContainer.setBorder(Constants.EMPTYBORDER);
        searchDialog.setContentPane(searchContainer);
        
        JPanel topContainer = new JPanel();
            topContainer.setLayout(new BoxLayout(topContainer, BoxLayout.LINE_AXIS));
            topContainer.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
            searchContainer.add(topContainer, BorderLayout.PAGE_START);
        JLabel lblFind = new JLabel(lang.getString("FINDTEXT"));
            topContainer.add(lblFind);
            topContainer.add(Box.createHorizontalStrut(10));
            topContainer.add(Box.createHorizontalGlue());
        txtFind = new JTextField(20);
            lblFind.setLabelFor(txtFind);
            topContainer.add(txtFind);
        topContainer.add(Box.createHorizontalStrut(10));
        JButton btnFind = new JButton(lang.getString("FIND"));
            btnFind.addActionListener(new ActionListener() {
               public void actionPerformed(ActionEvent e) { search(txtFind.getText()); } 
            });
            btnFind.setMinimumSize(Constants.MINSIZE);
            btnFind.setPreferredSize(Constants.PREFSIZE);
            btnFind.setMaximumSize(Constants.MAXSIZE);
            topContainer.add(btnFind);
            //searchContainer.add(Box.createRigidArea(new Dimension(400,400)),BorderLayout.CENTER);
            searchResults = createIconTable(new ArrayList<Account>(), searchContainer);
            searchResults.addMouseListener(new SearchResultsMouseListener());
            searchResultsScroll = new JScrollPane(searchResults);
                searchResultsScroll.getVerticalScrollBar().setUnitIncrement(10);
                searchContainer.add(searchResultsScroll,BorderLayout.CENTER);

        searchStatusBar = new JLabel();
            searchContainer.add(searchStatusBar, BorderLayout.SOUTH);
            
        searchDialog.getRootPane().setDefaultButton(btnFind);
        searchDialog.setMinimumSize(new Dimension(400,300));
        searchDialog.pack();
        searchDialog.setLocationRelativeTo(this);
        searchDialog.setVisible(true);
    }
    
    /**
     * Creates a JTable and fills it with accounts
     * @param accounts An ArrayList of Accounts
     * @param container The container this table will be added to (used to calculate the number of columns to show)
     * @return The completed JTable
     */
    private JTable createIconTable(ArrayList<Account> accounts, Container container) {
        JTable table = null;
        if(setViewMode == 'L' || setViewMode == 'S') { // Largeicons, Smallicons, lIst, Tiles
                int iconSize;
                if(setViewMode == 'L') {
                    iconSize = 96;
                } else {
                    iconSize = 64;
                }
                GroupTableModel tableModel = new GroupTableModel(accounts, container, iconSize, "icon");
                table = new JTable(tableModel);
                table.setShowGrid(false);
                table.setRowHeight(tableModel.getRowHeight());
                table.setCellSelectionEnabled(true);
                table.setTableHeader(null);
                table.setFillsViewportHeight(true);
                TableCellRenderer ImageRenderer = new GroupIconRenderer();
                int colWidth = tableModel.getColWidth();
                for(int i=0;i<table.getColumnCount();i++) {
                    TableColumn col = table.getColumnModel().getColumn(i);
                    col.setCellRenderer(ImageRenderer);
                    col.setPreferredWidth(colWidth);
                }
            } else if(setViewMode == 'T') {
                GroupTableModel tableModel = new GroupTableModel(accounts, container, 64, "tile");
                table = new JTable(tableModel);
                table.setShowGrid(false);
                table.setRowHeight(tableModel.getRowHeight());
                table.setCellSelectionEnabled(true);
                table.setTableHeader(null);
                table.setFillsViewportHeight(true);
                TableCellRenderer ImageRenderer = new GroupIconRenderer();
                int colWidth = tableModel.getColWidth();
                for(int i=0;i<table.getColumnCount();i++) {
                    TableColumn col = table.getColumnModel().getColumn(i);
                    col.setCellRenderer(ImageRenderer);
                    col.setPreferredWidth(colWidth);
                }
            } else if(setViewMode == 'I') {
                GroupTableListModel tableModel = new GroupTableListModel(accounts);
                table = new JTable(tableModel);
                table.setFillsViewportHeight(true);
                table.setAutoCreateRowSorter(true);
                table.setRowHeight(36);
                table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
                TableColumnModel colModel = table.getColumnModel();
                    colModel.getColumn(0).setCellRenderer(new Utils.IconRenderer());
                    colModel.getColumn(0).setMinWidth(36);
                    colModel.getColumn(0).setMaxWidth(36);
                    colModel.getColumn(4).setCellRenderer(new DateRenderer());
                    colModel.getColumn(5).setCellRenderer(new DateRenderer());
            }
        return table;
    }
    
    /**
     * Called by showSearchDialog(). Searches the accounts list for the specified text.
     * @param searchText The text to search for.
     */
    private void search(String searchText) {
        String[] terms = searchText.toLowerCase().split(" ");
        ArrayList<Account> matches = new ArrayList<Account>();
        String thisAccDetails;
        int numMatches = 0;
        for(Account account : accounts) {
            if(!account.getIsDeleted()) {
                thisAccDetails = account.getSearchString().toLowerCase();
                for(String term : terms) {
                    if(thisAccDetails.indexOf(term)>=0) {
                        matches.add(account);
                        numMatches++;
                    }
                }
            }
        }
        searchContainer.remove(searchResultsScroll);
        searchResults = createIconTable(matches, searchContainer);
        searchResults.addMouseListener(new SearchResultsMouseListener());
        searchResultsScroll = new JScrollPane(searchResults);
            searchResultsScroll.getVerticalScrollBar().setUnitIncrement(10);
            searchContainer.add(searchResultsScroll,BorderLayout.CENTER);
         if(numMatches > 0) {
             MessageFormat formatter = new MessageFormat(lang.getString("MATCHESFOUND"));
             searchStatusBar.setText(formatter.format(new Object[] {numMatches}));
        } else {
            searchStatusBar.setText(lang.getString("NOMATCHES"));
        }
        searchResults.revalidate();
        searchDialog.pack();
    }
    
    /**
     * Shows the 'Add Account' or 'Edit Account' dialog for an account.
     * @param account The account to be shown. If the name is blank, this is classed
     *                as a new account and the default values are used. Otherwise, the
     *                fields are filled using information from <u>selectedAccount</i>.
     */
    private void showAddDialog(Account account, boolean editing) {
        FrmAddAccount frmAdd = new FrmAddAccount(this, account, editing, lang, accountTree, types, accountGroups);
        frmAdd.setIconImage(this.getIconImage());
        if(frmAdd.getResult() == JOptionPane.OK_OPTION) {
            Account newAccount = frmAdd.getAccount();
            if(Utils.getArrayListIndex(newAccount.getName(), accounts) >= 0) {
                /* does not exist in the list, but has been deleted since the database has 
                 * been synchronised, so still exists in 'accounts' arraylist.
                 * Name must be the same because the account name exists */
                accounts.remove(Utils.getArrayListIndex(newAccount.getName(), accounts));
            }
            if(editing) {
                if(!account.getName().equals(newAccount.getName())) {
                    // if the name's the same, we can just update the details, done by the code above
                    // otherwise, hide the account but leave a reference to it so we can delete it from the DB
                    getNodeFromName(accountTreeRoot, account).removeFromParent();
                    accounts.get(Utils.getArrayListIndex(account.getName(),accounts)).delete();
                }
            }
            addAccount(newAccount);
        }
    }

    /**
     * Called by showAddDialog(). Processes the data entered
     * into the form and saves it to the accounts list.
     * <br>
     * Checks for duplicate nodes in both the JTree and the accounts list,
     * in case a node has been deleted from the tree but still exists in the list.
     * @param e The ActionEvent created by the Ok button.
     */
    private void addAccount(Account account) {
        accounts.add(account);
        refreshAccountTree();
        int newIndex = Utils.getArrayListIndex(account.getName(), accounts);
        accounts.get(newIndex);
        setSelectedAccount(accounts.get(Utils.getArrayListIndex(account.getName(), accounts)));
    }
    
    /**
     * Shows the 'Add Group' or 'Edit Group' dialog for an account.
     * @param group The account to be shown. If the name is blank, this is classed as
     *              a new group and the parent is set to the root node (LockCrypt).
     *              Otherwise, the name is filled using information from <u>selectedAccount</i>.
     */
    private void showAddGroupDialog(Group group, boolean editing) {
        FrmAddGroup frmAddGroup = new FrmAddGroup(this, group, accountGroups, editing, lang, accountTree);
        if(frmAddGroup.getResult() == JOptionPane.OK_OPTION) {
            String oldName = group.getName(),
                   newName = frmAddGroup.getGroupName();
            if(Utils.getTreeIndex(newName, accountTree) >= 0 && Utils.getArrayListIndex(newName,accountGroups)>=0) { //group exists in groups list (has been deleted since the database was loaded)
                accountGroups.remove(Utils.getArrayListIndex(newName, accountGroups));
            }
            addGroup(new Group(newName, frmAddGroup.getParentName(), true));
            if(editing) {
                getNodeFromName(accountTreeRoot, group).removeFromParent();
                accountGroups.get(Utils.getArrayListIndex(oldName, accountGroups)).delete();
                for(Account acc : accounts) {
                    if(acc.getGroup().equals(oldName)) {
                        acc.setGroup(newName);
                    }
                }
            }
            refreshAccountTree();
        }
    }
    
    /**
     * Adds a new group or edits an existing one, depending on the ActionCommand specified in <i>e</i>.
     * Allows for duplicate groups and groups which have been deleted from the list but still 
     * remain in the database.
     * @param e The ActionEvent created by the Ok button on the showAddGroupDialog form.
     */
    public void addGroup(Group group) {
        accountGroups.add(new Group(group.getName(), group.getParentName(), true));
        refreshAccountTree();   
    }
    
    /**
     * Removes the selected item, whether it's an ccount or group
     */
    private void removeSelected() {
        if(accountTree.hasFocus()) {
            DefaultMutableTreeNode node = accountTree.getSelectedNode();
            if(node instanceof Account) {
                removeAccount((Account)node);
            } else if(node instanceof Group) {
                removeGroup((Group)node, true);
            }
        } else if(groupTable.hasFocus()) {
            Object selectedVal = groupTable.getValueAt(groupTable.getSelectedRow(), groupTable.getSelectedColumn());
            if(selectedVal instanceof Account) {
                 removeAccount((Account)selectedVal);
            }
        }
    }
    
    /**
     * Deletes the specified group.
     * @param group The group to delete
     * @param refresh True if the tree should be refreshed afterwards, otherwise false
     */
    private void removeGroup(Group group, boolean refresh) {
        String grpName = group.getName();
        DefaultMutableTreeNode thisGroupNode = getNodeFromName(accountTreeRoot, grpName);
        if(thisGroupNode.getChildCount()==0 || !refresh || (thisGroupNode.getChildCount()>0 && JOptionPane.showConfirmDialog(mainWindow, lang.getString("DELGROUPDESC"), lang.getString("DELGROUP"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)) {
            int oldindex = accountTree.getSelectionRows()[0];
            accountGroups.get(Utils.getArrayListIndex(grpName,accountGroups)).delete();
            for(Enumeration children = thisGroupNode.children(); children.hasMoreElements();) {
                DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode)children.nextElement();
                if(thisNode instanceof Account) {
                    ((Account)thisNode).delete();
                } else if(thisNode instanceof Group) {
                    removeGroup((Group)thisNode, false);
                }
            }
            if(refresh) {
                refreshAccountTree();
                int numrows = accountTree.getRowCount(), newrow;
                if(oldindex>=numrows) {
                    newrow = numrows-1;
                } else {
                    newrow = oldindex;
                }
                processNode((DefaultMutableTreeNode)accountTree.getPathForRow(newrow).getLastPathComponent());
            }
        }
    }
    
    /**
     * Deletes the specified account.
     * @param account The account to delete
     */
    private void removeAccount(Account account) {
        String accName = account.getName();
        int oldindex = accountTree.getSelectionRows()[0];
        accounts.get(Utils.getArrayListIndex(accName,accounts)).delete();
        refreshAccountTree();
        DefaultMutableTreeNode newSelection, thisGroupNode = getNodeFromName(accountTreeRoot, account.getGroup());
        if(thisGroupNode.getChildCount()==0) { // this account is the last one in the group
            newSelection = thisGroupNode;
        } else {
            int numrows = accountTree.getRowCount(), newrow;
            if(oldindex>=numrows) {
                newrow = numrows-1;
            } else {
                newrow = oldindex;
            }
            newSelection = (DefaultMutableTreeNode)accountTree.getPathForRow(newrow).getLastPathComponent();
        }
        processNode(newSelection);
    }
    
    /**
     * <b>Text Mode:</b> Uses the key entered to decrypt the first few bytes of the file.
     * If the correct key was entered, 'LockCrypt' will be decrypted.
     * <b>MySQL Mode:</b> Takes the sqlIVSpec and encrypted String 'LockCrypt' read by promptPass()
     * If the password was correct, the deciphered string will read 'LockCrypt'.
     * @return True if the pass was correct, otherwise false.
     */
    private boolean processPass(boolean firstTry, IvParameterSpec sqlIvSpec) {
        String keyEntered;
        if(firstTry) {
            keyEntered = "";
        } else {
            keyEntered = new String(passSetJCEKey.getPassword());
        }
        String check = "";
        if(setDBMode.equals("text")) {
            String fileName;
            if(externalSettingsFile == null) {
                fileName = setFileName;
            } else {
                fileName = externalSettingsFile;
            }
            File settingsFile = new File(fileName);
            FileInputStream fis = null;
            try {
                if(settingsFile.exists()) {
                    Security.addProvider(new com.sun.crypto.provider.SunJCE());
                    SecretKey secretKey = Utils.getKeySpec(keyEntered);

                    fis = new FileInputStream(fileName);
                    BufferedInputStream bis = new BufferedInputStream(fis);

                    byte[] iv = new byte[16];
                    bis.read(iv, 0, 16);
                    IvParameterSpec ivSpec = new IvParameterSpec(iv);

                    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);

                    CipherInputStream cis = new CipherInputStream(bis, cipher);
                    //ObjectInputStream ois = new ObjectInputStream(cis);
                    
                    int c = 0;
                    StringBuffer buf = new StringBuffer();
                    while((c = cis.read()) != -1) {
                        char ch = (char)c;
                        if(ch == '\n' || ch == '\r' || c < 20) {
                            if(buf.length() > 0) {
                                if(buf.toString().equals(("LockCrypt"))) {
                                    check = "LockCrypt";
                                    break;
                                }
                                buf.setLength(0);
                            }
                        } else {
                            buf.append(ch);
                        }
                    }
                }
            } catch(Exception e) {
                System.out.println("Exception: "+e.getLocalizedMessage());
            } finally {
                if(fis != null) {
                    try {
                        fis.close();
                    } catch(Exception ignore) {}
                }
            }
        } else if(setDBMode.equals("mysql")) {
            try {
                Security.addProvider(new com.sun.crypto.provider.SunJCE());
                SecretKey secretKey = Utils.getKeySpec(keyEntered);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.DECRYPT_MODE, secretKey, sqlIvSpec);

                check = new String(cipher.doFinal(sqlEncryptedCheck));
            } catch(Exception e) {
                
            }
        }
        if(DEBUG) {
            System.out.println("Expected \""+"LockCrypt"+"\"\nFound    \""+check+"\" ("+sqlEncryptedCheck+")");
        }
        if(check.equals("LockCrypt")) {
            setJCEKey = keyEntered;
            setMenuItems(true);
            splitPane.setLeftComponent(accountTreeScroll);
            splitPane.setDividerLocation(prefs.getInt("setsplitlocation", this.getWidth()/3));
            maincontent.revalidate();
            loadDatabase(null, true);
            tmrAutoType.setInterval(setAutoLock);
            return true;
        } else if(!firstTry) {
            passSetJCEKey.selectAll();
            JOptionPane.showMessageDialog(mainWindow, lang.getString("ERR_WRONGPASS"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
            return false;
        }
        return false;
    }

    /**
     * Prompts for the password then calls processPass() when the Ok button is clicked.
     * If using <b>MySQL mode</b>, reads the IV and check values from the database 
     * and stores them. This prevents multiple queries to the database for the same information
     * if the wrong password is entered multiple times.
     */
    private void promptPass() {
        IvParameterSpec tempIvSpec = null;
        if(setDBMode.equals("mysql")) {
            // if MySQL mode, grab the encrypted password here, then we can try to decrypt it with the entered password without having to send another query every time
            Connection conn = null;
            try {
                Class.forName("com.mysql.jdbc.Driver").newInstance();
                conn = DriverManager.getConnection("jdbc:mysql://"+setDBHost+"/"+setDBName,setDBUser,setDBPass);
                Statement s = conn.createStatement();
                byte[] iv = new byte[16];
                try {
                    s.executeQuery("SELECT `value` FROM settings WHERE `name` = 'iv'");
                    ResultSet rs = s.getResultSet();
                    rs.next();
                    iv = rs.getBlob("value").getBytes(1, 16);
                } catch(Exception e) {
                    System.out.println("Could not retrieve IV: "+e.getLocalizedMessage());
                    if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                        setDBMode = "text";
                    }
                }
                //System.out.println("Loaded iv ("+iv.length+"): "+new String(iv));
                tempIvSpec = new IvParameterSpec(iv);

                s.executeQuery("SELECT `value` FROM settings WHERE `name` = 'check'");
                    ResultSet resCheck = s.getResultSet();
                    resCheck.next();
                    sqlEncryptedCheck = resCheck.getBytes("value");
            } catch(Exception e) {
                System.out.println("Exception 2: "+e.getLocalizedMessage());
            }
        }
        final IvParameterSpec sqlIvSpec = tempIvSpec;
        if(!processPass(true, sqlIvSpec)) {
            JPanel pnlHeader = new JPanel(new FlowLayout(FlowLayout.LEADING));
                pnlHeader.setBackground(Color.getHSBColor(0.58f, 0.1f, 0.95f));
                pnlHeader.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
                accountView.add(pnlHeader,BorderLayout.PAGE_START);
                JLabel lblName = new JLabel("<HTML><H1>"+lang.getString("ENTERPASS")+"</H1></HTML>");
                    lblName.setHorizontalTextPosition(JLabel.RIGHT);
                try {
                    ImageIcon icoName = new ImageIcon();
                    URL URLicon = this.getClass().getResource("images/icon_64.png");
                    icoName.setImage(Toolkit.getDefaultToolkit().getImage(URLicon));
                    lblName.setIcon(icoName);
                } catch(Exception e) { }
                pnlHeader.add(lblName);
            JPanel passPanel = new JPanel();
                passPanel.setLayout(new BoxLayout(passPanel, BoxLayout.LINE_AXIS));
                passPanel.setBorder(Constants.EMPTYBORDER);
                accountView.add(passPanel, BorderLayout.CENTER);
                passSetJCEKey = new JPasswordField();
                    passSetJCEKey.setMinimumSize(new Dimension(150,20));
                    passSetJCEKey.setPreferredSize(new Dimension(180,20));
                    passSetJCEKey.setMaximumSize(new Dimension(200,20));
                    passPanel.add(Box.createHorizontalGlue());
                    passPanel.add(passSetJCEKey);
                    passPanel.add(Box.createHorizontalStrut(10));    

                JButton btnOk = new JButton(lang.getString("OK"));
                    btnOk.addActionListener(new ActionListener() {
                       public void actionPerformed(ActionEvent e) {
                           processPass(false, sqlIvSpec);
                       }
                    });
                    btnOk.setMinimumSize(Constants.MINSIZE);
                    btnOk.setPreferredSize(Constants.PREFSIZE);
                    btnOk.setMaximumSize(Constants.MAXSIZE);
                    passPanel.add(btnOk);
                    passPanel.add(Box.createHorizontalGlue());
                    passPanel.getRootPane().setDefaultButton(btnOk);
            this.pack();
            this.repaint();
            passSetJCEKey.requestFocusInWindow();
        }
    }
    
    /**
     * Reads the preferences from a Preferences object, then either shows the 
     * welcome screen if this is the first load, or prompts for the password.
     */
    private void loadSettings() {
        setDBMode = prefs.get("setdbmode", "text");
        setFileName = prefs.get("setfilename", System.getenv("APPDATA")+"\\LockCrypt\\LockCrypt.lce");
        setDBHost = prefs.get("setdbhost", "");
        setDBUser = prefs.get("setdbuser", "");
        setDBPass = prefs.get("setdbpass", "");
        setDBName = prefs.get("setdbname", "");
        setFirstLoad = prefs.getBoolean("setfirstload", true);
        setSecureClipboard = prefs.getBoolean("setsecureclipboard", true);
        setViewMode = (char)prefs.getInt("setviewmode", (int)'L');
        setSortMode = (char)prefs.getInt("setsortmode", (int)'N');
        setSortOrder = (char)prefs.getInt("setsortorder", (int)'A');
        setSecureClipDelay = prefs.getInt("setsecureclipdelay", 10);
        setLang = prefs.get("setlang", "en_GB");
        lang = Utils.getLanguage(setLang);
        System.out.println(lang.getLocale());
        System.out.println(lang.getLocale().getCountry());
        if(lang.getLocale().getCountry().equals("zh") || lang.getLocale().getCountry().equals("ka") || lang.getLocale().getCountry().equals("he")) {
            Utils.getSafeFont(setLang, true);
        }
        
        setSizeMaximized = prefs.getBoolean("setsizemaximized", false);
        setSizeWidth = prefs.getInt("setsizewidth",700);
        setSizeHeight = prefs.getInt("setsizeheight", 500);
        setSizePosition = prefs.get("setsizeposition", "100,100");
        setLastExportLocation = prefs.get("setlastexportlocation", System.getenv("USERPROFILE"));
        setLastDBLocation = prefs.get("setlastdblocation", System.getenv("USERPROFILE"));
        setLaf = prefs.get("setlaf", "JGoodies");
        setMinimizeToTray = prefs.getBoolean("setminimizetotray", true);
        setEnableFirefox = prefs.getBoolean("setenablefirefox", true);
        setEnableAutoType = prefs.getBoolean("setenableautotype", true);
        setAutoTypeHotkey = prefs.get("setautotypehotkey", "ctrl+shift+T");
        setAutoLock = prefs.getInt("setautolock", 600);
        if(setEnableFirefox) {
            startFirefoxServer();
        } else {
            if(firefoxServerThread != null && firefoxServerThread.isRunning()) {
                stopFirefoxServer();
            }
        }
        updateLookNFeel();
    }
    
    
    /**
     * Loads the list of accounts and account types from the database
     * @param fileName The filename to write to (default if loading the default database, otherwise the name of the file to import)
     */
    public void loadDatabase(final String fileName, final boolean utf8) {
        createProgressBar(lang.getString("LOADING"));
        frameProgressBar.setLocationRelativeTo(this);
        progressBar.setIndeterminate(true);
        SwingWorker worker = new SwingWorker<Void, Void>() {
            public Void doInBackground() {
                setProgress(0);
                if(fileName == null) {
                    /* We're loading the database, not importing */
                    accounts.clear();
                    types.clear();
                    accountGroups.clear();
                }
                boolean errorLoading = false;
                while(setJCEKey == null) {
                    try {
                        Thread.sleep(500);
                    } catch(InterruptedException e) {
                        System.out.println(e.getLocalizedMessage());
                    }
                }
                if(setDBMode.equals("text") || (setDBMode.equals("mysql") && fileName != null)) {
                    String settingsFileName = null;
                    if(fileName == null) {
                        /* Saving on shutdown/relock */
                        if(externalSettingsFile != null && externalSettingsFile.length() > 0) {
                            settingsFileName = externalSettingsFile;
                        } else {
                            settingsFileName = setFileName;
                        }
                    } else {
                        /* exporting as an LCE database */
                        settingsFileName = fileName;
                    }
                    File settingsFile = new File(settingsFileName);
                    try {
                        if(settingsFile.exists()) {
                            String encryptionKey = null;
                            if(fileName == null) {
                                encryptionKey = setJCEKey;
                            } else {
                                String fileKey = PasswordDialog.ShowPasswordDialog(null, lang.getString("ENTERPASS_DESC"), lang.getString("ENTERPASS"));
                                if(fileKey == null) { // user cancelled
                                    return null;
                                } else {
                                    encryptionKey = fileKey;
                                }
                            }
                            Security.addProvider(new com.sun.crypto.provider.SunJCE());
                            SecretKeySpec secretKey = Utils.getKeySpec(encryptionKey);

                            FileInputStream fis = new FileInputStream(settingsFile);
                            BufferedInputStream bis = new BufferedInputStream(fis);

                            byte[] iv = new byte[16];
                            bis.read(iv, 0, 16);
                            IvParameterSpec ivSpec = new IvParameterSpec(iv);
                            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
                            CipherInputStream cis = new CipherInputStream(bis, cipher);

                            ArrayList<String> unencrypted = new ArrayList<String>();
                           
                            int c = 0;
                            StringBuffer buf = new StringBuffer();
                            ByteBuffer bbuf = ByteBuffer.allocate(1000);
                            if(utf8) {
                                while((c = cis.read()) != -1) {
                                    char ch = (char)c;
                                    if(ch == '\n' || ch == '\r' || c < 20) {
                                        if(bbuf.position() > 0) {
                                            if(buf.length() > 0) {
                                                unencrypted.add(buf.toString()+new String(bbuf.array(), "UTF8").trim());
                                                buf = new StringBuffer();
                                            } else {
                                                unencrypted.add(new String(bbuf.array(), "UTF8").trim());
                                            }
                                            bbuf.clear();
                                            for(int i=0;i<bbuf.capacity();i++) {
                                                bbuf.put((byte)0);
                                            }
                                            bbuf.clear();
                                        }
                                    } else {
                                        bbuf.put((byte)c);
                                        if(bbuf.position() == bbuf.capacity()) {
                                            buf.append(new String(bbuf.array(), "UTF8").trim());
                                            bbuf.clear();
                                            for(int i=0;i<bbuf.capacity();i++) {
                                                bbuf.put((byte)0);
                                            }
                                            bbuf.clear();
                                        }
                                    }
                                }
                            } else {
                                while((c = cis.read()) != -1) {
                                    char ch = (char)c;
                                    if(ch == '\n' || ch == '\r' || c < 20) {
                                        if(bbuf.position() > 0) {
                                            if(buf.length() > 0) {
                                                unencrypted.add(buf.toString()+new String(bbuf.array()).trim());
                                                buf = new StringBuffer();
                                            } else {
                                                unencrypted.add(new String(bbuf.array()).trim());
                                            }
                                            bbuf.clear();
                                            for(int i=0;i<bbuf.capacity();i++) {
                                                bbuf.put((byte)0);
                                            }
                                            bbuf.clear();
                                        }
                                    } else {
                                        bbuf.put((byte)c);
                                        if(bbuf.position() == bbuf.capacity()) {
                                            buf.append(new String(bbuf.array()).trim());
                                            bbuf.clear();
                                            for(int i=0;i<bbuf.capacity();i++) {
                                                bbuf.put((byte)0);
                                            }
                                            bbuf.clear();
                                        }
                                    }
                                }
                            }
                            if(!unencrypted.get(0).equals("LockCrypt")) {
                                JOptionPane.showMessageDialog(mainWindow, lang.getString("ERR_WRONGPASS"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                            } else {
                                JSONObject jso;
                                for(String inp : unencrypted) {
                                    if(inp.length() > 3) {
                                        try {
                                            String type = inp.substring(0,3);
                                                //System.out.println(inp);
                                                //System.out.println(inp+"\n"+type+"\n");
                                            if(type.equals("acc")) {
                                                jso = new JSONObject(inp.substring(3));
                                                LinkedHashMap<String,String> fields = new LinkedHashMap<String,String>();
                                                JSONArray jsoFields = jso.getJSONArray("Fields");
                                                JSONObject currentField;
                                                int numFields = jsoFields.length();
                                                for(int i=0;i<numFields;i++) {
                                                    currentField = jsoFields.getJSONObject(i);
                                                    fields.put(currentField.getString("Name"), currentField.getString("Value"));
                                                }
                                                AccType accType = new AccType(jso.getString("Type"), fields, jso.getBoolean("ShowNotes"), jso.getString("Notes"));
                                                accType.setID(jso.getString("TypeID"));
                                                Account acc = new Account(jso.getString("Name"),jso.getString("Type"), jso.getString("Icon"), accType, false, jso.getString("Parent"), jso.getLong("Created"), jso.getLong("Modified"));
                                                accounts.add(acc);
                                            } else if(type.equals("typ")) {
                                                jso = new JSONObject(inp.substring(3));
                                                JSONArray jsoFields = jso.getJSONArray("Fields");
                                                int numFields = jsoFields.length();
                                                ArrayList<String> fieldNames = new ArrayList<String>(numFields);
                                                for(int i=0;i<numFields;i++) {
                                                    fieldNames.add(jsoFields.getString(i));
                                                }
                                                String[] fields = new String[numFields];
                                                fieldNames.toArray(fields);
                                                AccType accType = new AccType(jso.getString("Name"), fields, false, jso.getBoolean("IsProtected"), jso.getBoolean("ShowNotes"));
                                                accType.setID(jso.getString("ID"));
                                                types.add(accType);
                                            } else if(type.equals("grp")) {
                                                jso = new JSONObject(inp.substring(3));
                                                String name = jso.getString("Name");
                                                if(!name.equals("LockCrypt")) { // trying to add the root node to itself would be like going back in time and becoming your own grandfather
                                                    accountGroups.add(new Group(name, jso.getString("Parent"), false, jso.getLong("Created"), jso.getLong("Modified")));
                                                }
                                            }
                                        } catch(Exception e) {
                                            JOptionPane.showMessageDialog(mainWindow, lang.getString("ERR_LOADSETTINGS")+": "+settingsFileName+"\n"+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch(Exception e) {
                        JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+"#217: "+lang.getString("ERR_LOADSETTINGS")+": "+settingsFileName+"\n"+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                        e.printStackTrace();
                    }
                    setStatusText(settingsFileName);
                } else if(setDBMode.equals("mysql")) {
                    Connection conn = null;
                    ResultSet resGroups = null, resTypes = null, resAccounts = null;
                    String accName, accType, accIcon, accGroup, notes;
                    long accCreated = 0, accModified = 0;
                    boolean showNotes;
                    try {
                        Class.forName("com.mysql.jdbc.Driver").newInstance();
                        conn = DriverManager.getConnection("jdbc:mysql://"+setDBHost+"/"+setDBName,setDBUser,setDBPass);
                        Statement s = conn.createStatement();
                        byte[] iv = new byte[16];
                        try {
                            s.executeQuery("SELECT `value` FROM settings WHERE `name` = 'iv'");
                            ResultSet rs = s.getResultSet();
                            rs.next();
                            //iv = rs.getBytes("value"); //.getBlob(iv);
                            iv = rs.getBlob("value").getBytes(1, 16);
                        } catch(Exception e) {
                            JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+"#218: "+lang.getString("ERR_READIV")+"\n"+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                        }
                        Security.addProvider(new com.sun.crypto.provider.SunJCE());
                        SecretKey secretKey = Utils.getKeySpec(setJCEKey);
                        IvParameterSpec ivSpec = new IvParameterSpec(iv);

                        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
                        
                        s.executeQuery("SELECT * FROM groups");
                        resGroups = s.getResultSet();
                        while(resGroups.next()) {
                            accountGroups.add(new Group(
                                    new String(cipher.doFinal(resGroups.getBytes("name")), "utf8"), 
                                    new String(cipher.doFinal(resGroups.getBytes("parent")), "utf8"), 
                                    false,
                                    Long.parseLong(new String(cipher.doFinal(resGroups.getBytes("created")))),
                                    Long.parseLong(new String(cipher.doFinal(resGroups.getBytes("modified"))))
                            ));
                        }
                        s.executeQuery("SELECT * FROM types");
                        resTypes = s.getResultSet();
                        String[] fields;
                        JSONArray jsaFields;
                        while(resTypes.next()) {
                            jsaFields = new JSONArray(new String(cipher.doFinal(resTypes.getBytes("fields"))));
                            fields = new String[jsaFields.length()];
                            for(int i=0;i<fields.length;i++) {
                                fields[i] = jsaFields.getString(i);
                            }
                            types.add(new AccType(
                                    new String(cipher.doFinal(resTypes.getBytes("name")), "utf8"),
                                    fields,
                                    false,
                                    Boolean.parseBoolean(new String(cipher.doFinal(resTypes.getBytes("isProtected")))),
                                    Boolean.parseBoolean(new String(cipher.doFinal(resTypes.getBytes("showNotes")), "utf8"))
                            ));
                        }
                        s.executeQuery("SELECT * FROM accounts");
                        resAccounts = s.getResultSet();
                        JSONArray jsoFields;
                        JSONObject currentField;
                        AccType accountType;
                        while(resAccounts.next()) {
                            accName = new String(cipher.doFinal(resAccounts.getBytes("name")), "utf8");
                            accType = new String(cipher.doFinal(resAccounts.getBytes("type")), "utf8");
                            accIcon = new String(cipher.doFinal(resAccounts.getBytes("icon")), "utf8");
                            accGroup = new String(cipher.doFinal(resAccounts.getBytes("group")), "utf8");
                            accCreated = Long.parseLong(new String(cipher.doFinal(resAccounts.getBytes("created"))));
                            accModified = Long.parseLong(new String(cipher.doFinal(resAccounts.getBytes("modified"))));
                            showNotes = Boolean.parseBoolean(new String(cipher.doFinal(resAccounts.getBytes("showNotes"))));
                            notes = new String(cipher.doFinal(resAccounts.getBytes("notes")), "utf8");
                            if(DEBUG) {
                                System.out.println("Acc: "+accName+", "+accType+", "+accIcon+", "+accGroup+", "+accCreated+", "+accModified);
                            }
                            jsoFields = new JSONArray(new String(cipher.doFinal(resAccounts.getBytes("fields")), "utf8"));
                            int numFields = jsoFields.length();
                            LinkedHashMap<String, String> fieldMap = new LinkedHashMap<String, String>();
                            for(int i=0;i<numFields;i++) {
                                currentField = jsoFields.getJSONObject(i);
                                fieldMap.put(currentField.getString("Name"), currentField.getString("Value"));
                            }
                            accountType = new AccType(accType,fieldMap, showNotes, notes);
                            accountType.setID(new String(cipher.doFinal(resAccounts.getBytes("typeid")), "utf8"));
                            accounts.add(new Account(accName, accType, accIcon, accountType, false, accGroup, accCreated, accModified));
                        }
                        setStatusText(setDBHost);
                    } catch(ClassNotFoundException e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Class not found: "+e.getLocalizedMessage());
                        }
                    } catch(InstantiationException e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Could not initialize database driver: "+e.getLocalizedMessage());
                        }
                    } catch(IllegalAccessException e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Could not initialize database driver: "+e.getLocalizedMessage());
                        }
                    } catch(SQLException e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("SQLException: " + e.getLocalizedMessage());
                            System.out.println("SQLState: " + e.getSQLState());
                            System.out.println("VendorError: " + e.getErrorCode());
                        }
                    } catch(Exception e) {
                        if(JOptionPane.showConfirmDialog(mainWindow, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Other Exception: \n"+e.getClass()+"\n"+e.getCause()+"\n" + e.getLocalizedMessage());
                        }
                    } finally {
                        if(conn != null) {
                            try {
                                conn.close();
                            } catch(SQLException ignore) {}
                        }
                    }
                }
                if(errorLoading) {
                    JOptionPane.showMessageDialog(mainWindow, lang.getString("ERR_ACCOUNTSNOTLOADED_DESC"), lang.getString("ERR_ACCOUNTSNOTLOADED"), JOptionPane.WARNING_MESSAGE);
                }
                return null;
            }
            @Override
            public void done() {
                refreshAccountTree();
                
                frameProgressBar.dispose();
                accountTree.setSelectionRow(0);
                setSelectedGroup((Group)accountTreeRoot);
            }
        };
        worker.addPropertyChangeListener(new ProgressListener());
        worker.execute();
    }

    /**
     * Saves the database
     * @param fileName The file name to write to
     * @param quitAfter True if the program should exit after saving
     * @param utf8 True if UTF8 should be used to encode the file
     * @return True on success, otherwise false
     */
    public boolean saveDatabase(final String fileName, final boolean quitAfter, final boolean utf8) {
        createProgressBar(lang.getString("SAVINGSETTINGS"));
        SwingWorker worker = new SwingWorker<Boolean, Void>() {
            public Boolean doInBackground() {
                if(setDBMode.equals("text") || (setDBMode.equals("mysql") && fileName != null)) {
                    String settingsFileName = null;
                    if(fileName == null) {
                        if(externalSettingsFile != null && externalSettingsFile.length() > 0) {
                            settingsFileName = externalSettingsFile;
                        } else {
                            settingsFileName = setFileName;
                        }
                    } else {
                        settingsFileName = fileName;
                    }
                    File settingsFile = new File(settingsFileName);
                    boolean cancel = false;
                    if(!settingsFile.exists()) {
                        try {
                            File dir = new File(settingsFile.getParent());
                            if(!dir.exists()) {
                                dir.mkdirs();
                            }
                            if(!settingsFile.createNewFile()) {
                                if(DEBUG) {
                                    System.out.println("Could not create file "+settingsFile+" (no permission)");
                                }
                                cancel = true;
                                JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+"#219: "+lang.getString("ERR_CANTSAVEFILE")+": "+settingsFileName+" (Error #4b)", lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                            }
                        } catch(Exception ex) {
                            if(DEBUG) {
                                System.out.println("Could not create file "+settingsFile+"\n"+ex.getLocalizedMessage());
                            }
                            cancel = true;
                            JOptionPane.showMessageDialog(mainWindow, lang.getString("ERROR")+"#220: "+lang.getString("ERR_CANTSAVEFILE")+": "+settingsFileName+" (Error #4a)", lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                        }
                    }
                    if(!cancel) {
                        try {
                            Security.addProvider(new com.sun.crypto.provider.SunJCE());
                            SecretKeySpec secretKey = Utils.getKeySpec(setJCEKey);
                            byte[] iv = new byte[16];
                            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
                            sr.nextBytes(iv);
                            IvParameterSpec ivSpec = new IvParameterSpec(iv);

                            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);

                            FileOutputStream fos = new FileOutputStream(settingsFileName);
                            BufferedOutputStream bos = new BufferedOutputStream(fos);
                            bos.write(iv);
                            bos.flush();

                            CipherOutputStream cos = new CipherOutputStream(bos, cipher);
                            //BufferedOutputStream cos = new BufferedOutputStream(bos);

                            if(utf8) {
                                cos.write("LockCrypt\n".getBytes("UTF8"));
                                cos.write(("LCV"+Constants.LC_VER+"\n").getBytes("UTF8"));
                                Collections.sort(types, new Comparator<AccType>() {
                                   public int compare(AccType type1, AccType type2) {
                                       return type1.getName().compareTo(type2.getName());
                                   }
                                });
                                for(AccType acctype : types) {
                                    if(!acctype.isDeleted) {
                                        cos.write((acctype.getOutput()+"\n").getBytes("UTF8"));
                                        if(DEBUG) {
                                            System.out.println(acctype.getOutput());
                                        }
                                    }
                                }
                                for(Group group : accountGroups) {
                                    if(!group.getIsDeleted()) {
                                        cos.write((group.getOutput()+"\n").getBytes("UTF8"));
                                        if(DEBUG) {
                                            System.out.println(group.getOutput());
                                        }
                                    }
                                }
                                Collections.sort(accounts, new Comparator<Account>() {
                                   public int compare(Account acc1, Account acc2) {
                                       return acc1.getName().compareTo(acc2.getName());
                                   }
                                });
                                for(Account acc : accounts) {
                                    if(!acc.getIsDeleted()) {
                                        cos.write((acc.getOutput(true)+"\n").getBytes("UTF8"));
                                        if(DEBUG) {
                                            System.out.println(acc.getOutput(true));
                                        }
                                    }
                                }
                            } else {
                                cos.write("LockCrypt\n".getBytes());
                                cos.write(("LCV"+Constants.LC_VER+"\n").getBytes());
                                for(AccType acctype : types) {
                                    if(!acctype.isDeleted) {
                                        cos.write((acctype.getOutput()+"\n").getBytes());
                                        if(DEBUG) {
                                            System.out.println(acctype.getOutput());
                                        }
                                    }
                                }
                                for(Group group : accountGroups) {
                                    if(!group.getIsDeleted()) {
                                        cos.write((group.getOutput()+"\n").getBytes());
                                        if(DEBUG) {
                                            System.out.println(group.getOutput());
                                        }
                                    }
                                }
                                Collections.sort(accounts, new Comparator<Account>() {
                                   public int compare(Account acc1, Account acc2) {
                                       return acc1.getName().compareTo(acc2.getName());
                                   }
                                });
                                for(Account acc : accounts) {
                                    if(!acc.getIsDeleted()) {
                                        cos.write((acc.getOutput(true)+"\n").getBytes());
                                        if(DEBUG) {
                                            System.out.println(acc.getOutput(true));
                                        }
                                    }
                                }
                            }
                            cos.close();
                        } catch(Exception e) {
                            if(DEBUG) {
                                System.out.println("Could not write to file "+settingsFile+"\n"+e.getLocalizedMessage());
                            }
                            Object[] options = { "Yes", "No" };
                            if(JOptionPane.showOptionDialog(mainWindow, lang.getString("ERROR")+"#221: "+lang.getString("ERR_CANTSAVEFILE")+": "+settingsFileName+"\n"+e.getMessage(), lang.getString("ERROR"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[1]) == 1) {
                                //TODO: cancel quit after, update lang message
                            }
                        }
                    }
                } else if(setDBMode.equals("mysql")) {
                    Connection conn = null;
                    try {
                        Class.forName("com.mysql.jdbc.Driver").newInstance();
                        conn = DriverManager.getConnection("jdbc:mysql://"+setDBHost+"/"+setDBName,setDBUser,setDBPass);
                        Statement s = conn.createStatement();
                        
                        //s.executeQuery("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '"+setDBName+"' AND TABLE_NAME = 'settings'");
                        byte[] iv = new byte[16];
                        boolean insertCheck = false; // if No IV is found, we need to insert the check value after the cipher has been intialized
                        String sql;
                        if(refreshDB) {
                            s.executeUpdate("DROP TABLE IF EXISTS `settings`");
                            s.executeUpdate("DROP TABLE IF EXISTS `accounts`");
                            s.executeUpdate("DROP TABLE IF EXISTS `groups`");
                            s.executeUpdate("DROP TABLE IF EXISTS `types`");
                        }
                        try {
                            s.executeQuery("SELECT `value` FROM `settings` WHERE `name` = 'iv'");
                            ResultSet rs = s.getResultSet();
                            while(rs.next()) {
                                    iv = rs.getBlob("value").getBytes(1, 16);
                            }
                        } catch(SQLException e) {
                            //System.out.println("IV not found: "+e.getLocalizedMessage());
                            //the column wasn't found (IV hasn't been stored yet)
                            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
                            sr.nextBytes(iv);
                            s.executeUpdate("CREATE TABLE IF NOT EXISTS `settings` ("+
                                            "`name` varchar(255) NOT NULL,"+
                                            "`value` blob NOT NULL,"+
                                            "PRIMARY KEY  (`name`),"+
                                            "UNIQUE KEY `uniquename` (`name`)"+
                                            ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
                            sql = "INSERT INTO `settings` (`name`,`value`) VALUES ('iv', ?)";
                            PreparedStatement ivstmt = conn.prepareStatement(sql);
                                ivstmt.setBytes(1, iv);
                                ivstmt.executeUpdate();
                                ivstmt.close();
                            insertCheck = true;
                        }
                        //System.out.println("Saved iv ("+iv.length+"): "+new String(iv));
                        Security.addProvider(new com.sun.crypto.provider.SunJCE());
                        SecretKey secretKey = Utils.getKeySpec(setJCEKey);
                        IvParameterSpec ivSpec = new IvParameterSpec(iv);
                        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
                        
                        if(insertCheck) {
                            sql = "INSERT INTO `settings` (`name`,`value`) VALUES ('check', ?)";
                            PreparedStatement ivstmt = conn.prepareStatement(sql);
                                ivstmt.setBytes(1, cipher.doFinal(new String("LockCrypt").getBytes("UTF8")));
                                ivstmt.executeUpdate();
                                ivstmt.close();
                        }
                        lblProgress.setText("Saving types");
                        s.executeUpdate("CREATE TABLE IF NOT EXISTS `types` ("+
                                        "`name` blob NOT NULL,"+
                                        "`id` blob NOT NULL,"+
                                        "`fields` blob NOT NULL,"+
                                        "`showNotes` blob NOT NULL,"+
                                        "`isProtected` blob NOT NULL"+
                                        ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
                        setProgress(10);
                        for(AccType acctype : types) {
                            if(acctype.isChanged || refreshDB) {
                                sql = "DELETE FROM `types` WHERE name = ?";
                                PreparedStatement delstmt = conn.prepareStatement(sql);
                                delstmt.setBytes(1, cipher.doFinal(acctype.getName().getBytes("UTF8")));
                                delstmt.executeUpdate();
                                delstmt.close();
                                if(!acctype.isDeleted || refreshDB) {
                                    sql = "INSERT INTO `types` (`name`,`id`,`fields`,`showNotes`,`isProtected`) VALUES (?, ?, ?, ?, ?)";
                                    PreparedStatement insstmt = conn.prepareStatement(sql);
                                    insstmt.setBytes(1, cipher.doFinal(acctype.getName().getBytes("UTF8")));
                                    insstmt.setBytes(2, cipher.doFinal(acctype.getID().getBytes("UTF8")));
                                    insstmt.setBytes(3, cipher.doFinal(new JSONArray(acctype.getFields()).toString().getBytes("UTF8")));
                                    insstmt.setBytes(4, cipher.doFinal(Boolean.toString(acctype.getShowNotes()).getBytes("UTF8")));
                                    insstmt.setBytes(5, cipher.doFinal(Boolean.toString(acctype.getIsProtected()).getBytes("UTF8")));
                                    insstmt.executeUpdate();
                                    insstmt.close();
                                }
                            }
                        }
                        s.executeUpdate("CREATE TABLE IF NOT EXISTS `groups` ("+
                                        "`name` blob NOT NULL,"+
                                        "`parent` blob NOT NULL,"+
                                        "`created` blob NOT NULL,"+
                                        "`modified` blob NOT NULL"+
                                        ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
                        lblProgress.setText("Saving groups");
                        setProgress(40);
                        for(Group group : accountGroups) {
                            if(group.getIsChanged() || refreshDB) {
                                sql = "DELETE FROM `groups` WHERE name = ?";
                                PreparedStatement delstmt = conn.prepareStatement(sql);
                                delstmt.setBytes(1, cipher.doFinal(group.getName().getBytes("UTF8")));
                                delstmt.executeUpdate();
                                delstmt.close();
                                if(!group.getIsDeleted() || refreshDB) {
                                    sql = "INSERT INTO `groups` (`name`,`parent`,`created`,`modified`) VALUES (?,?,?,?)";
                                    PreparedStatement insstmt = conn.prepareStatement(sql);
                                    insstmt.setBytes(1, cipher.doFinal(group.getName().getBytes("UTF8")));
                                    insstmt.setBytes(2, cipher.doFinal(group.getParentName().getBytes("UTF8")));
                                    insstmt.setBytes(3, cipher.doFinal(Long.toString(group.getCreated()).getBytes("UTF8")));
                                    insstmt.setBytes(4, cipher.doFinal(Long.toString(group.getModified()).getBytes("UTF8")));
                                    insstmt.executeUpdate();
                                    insstmt.close();
                                }
                            }
                        }
                        lblProgress.setText(lang.getString("SAVINGACCOUNTS"));
                        setProgress(60);
                        s.executeUpdate("CREATE TABLE IF NOT EXISTS `accounts` ("+
                                        "`name` blob NOT NULL,"+
                                        "`type` blob NOT NULL,"+
                                        "`typeid` blob NOT NULL,"+
                                        "`icon` blob NOT NULL,"+
                                        "`group` blob NOT NULL,"+
                                        "`created` blob NOT NULL,"+
                                        "`modified` blob NOT NULL,"+
                                        "`showNotes` blob NOT NULL,"+
                                        "`notes` blob NOT NULL,"+
                                        "`fields` blob NOT NULL"+
                                        ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
                        setProgress(70);
                        for(Account acc : accounts) {
                            if(acc.getIsChanged() || refreshDB) {
                                sql = "DELETE FROM `accounts` WHERE name = ?";
                                PreparedStatement delstmt = conn.prepareStatement(sql);
                                delstmt.setBytes(1, cipher.doFinal(acc.getName().getBytes("UTF8")));
                                delstmt.executeUpdate();
                                delstmt.close();
                                if(!acc.getIsDeleted() || refreshDB) {
                                    sql = "INSERT INTO `accounts` (`name`,`type`,`typeid`,`icon`,`group`,`created`,`modified`,`showNotes`,`notes`,`fields`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
                                    PreparedStatement insstmt = conn.prepareStatement(sql);
                                    insstmt.setBytes(1, cipher.doFinal(acc.getName().getBytes("UTF8")));
                                    insstmt.setBytes(2, cipher.doFinal(acc.getType().getBytes("UTF8")));
                                    insstmt.setBytes(3, cipher.doFinal(acc.getTypeID().getBytes("UTF8")));
                                    insstmt.setBytes(4, cipher.doFinal(acc.getIcon().getBytes("UTF8")));
                                    insstmt.setBytes(5, cipher.doFinal(acc.getGroup().getBytes("UTF8")));
                                    insstmt.setBytes(6, cipher.doFinal(Long.toString(acc.getCreated()).getBytes("UTF8")));
                                    insstmt.setBytes(7, cipher.doFinal(Long.toString(acc.getModified()).getBytes("UTF8")));
                                    insstmt.setBytes(8, cipher.doFinal(Boolean.toString(acc.getShowNotes()).getBytes("UTF8")));
                                    insstmt.setBytes(9, cipher.doFinal(acc.getNotes().getBytes("UTF8")));
                                    insstmt.setBytes(10, cipher.doFinal(acc.getOutput(false).getBytes("UTF8")));
                                    insstmt.executeUpdate();
                                    insstmt.close();
                                }
                            }
                        }
                        lblProgress.setText(lang.getString("DONE"));
                        setProgress(100);
                    } catch(ClassNotFoundException e) {
                        if(JOptionPane.showConfirmDialog(null, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Class not found: "+e.getLocalizedMessage());
                        }
                    } catch(InstantiationException e) {
                        if(JOptionPane.showConfirmDialog(null, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Could not initialize database driver: "+e.getLocalizedMessage());
                        }
                    } catch(IllegalAccessException e) {
                        if(JOptionPane.showConfirmDialog(null, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Could not initialize database driver: "+e.getLocalizedMessage());
                        }
                    } catch(SQLException e) {
                        if(JOptionPane.showConfirmDialog(null, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("SQLException: " + e.getLocalizedMessage());
                            System.out.println("SQLState: " + e.getSQLState());
                            System.out.println("VendorError: " + e.getErrorCode());
                        }
                    } catch(Exception e) {
                        if(JOptionPane.showConfirmDialog(null, lang.getString("ERR_DBCONNECT"), lang.getString("ERR_DBERROR"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
                            setDBMode = "text";
                        }
                        if(DEBUG) {
                            System.out.println("Other Exception: \n"+e.getClass()+"\n"+e.getCause()+"\n" + e.getLocalizedMessage());
                        }
                    } finally {
                        if(conn != null) {
                            try {
                                conn.close();
                            } catch(SQLException ignore) {}
                        }
                    }
                }
                return true;
            }
            @Override
            public void done() {
                frameProgressBar.setCursor(null);
                if(quitAfter) {
                    System.exit(0);
                } else {
                    frameProgressBar.dispose();
                }
            }
           
        };
        worker.addPropertyChangeListener(new ProgressListener());
        worker.execute();
        if(!quitAfter) {
            try {
                worker.get();
            } catch(Exception ignore) {/* wait for the database to save */}
        }
        return true;
    }
    
    /**
     * Called for each account/group by refreshTreeGroups. 
     * Looks for a node whose name matches <b>groupName</b>, starting at <b>startingNode</b>.
     * When found, adds <b>nodeToAdd</b>
     * @param nodeToAdd The Node to be added
     * @param startingNode The node at which to start the search for the parent node.
     * @param groupName The name of the parent node (the group) to which the new node should be added.
     * @return
     */
    private boolean addNodeToGroup(DefaultMutableTreeNode nodeToAdd, DefaultMutableTreeNode startingNode, String groupName) {
        if(startingNode.toString().equals(groupName)) {
            startingNode.add(nodeToAdd);
        } else {
            for(Enumeration theChildren = startingNode.children(); theChildren.hasMoreElements();) {
                DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode)theChildren.nextElement();
                if(thisNode.toString().equals(groupName)) {
                    thisNode.add(nodeToAdd);
                    return true;
                } else {
                    if(thisNode.getChildCount()>0) {
                        for(Enumeration grandChildren = thisNode.children(); grandChildren.hasMoreElements();) {
                            DefaultMutableTreeNode grandChild = (DefaultMutableTreeNode)grandChildren.nextElement();
                            if(addNodeToGroup(nodeToAdd, grandChild, groupName))  {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }
    
    /** Creates a JPanel containing a JProgressBar and the specified text.
     * @param title The title of the frame and text to show
     */
    private void createProgressBar(String title) {
        frameProgressBar = new JFrame(title);
            frameProgressBar.setIconImage(this.getIconImage());
        JPanel pnlProgress = new JPanel(new BorderLayout());
            frameProgressBar.setContentPane(pnlProgress);
        lblProgress = new JLabel(title+"...");
            pnlProgress.setBorder(Constants.EMPTYBORDER);
            pnlProgress.add(lblProgress,BorderLayout.PAGE_START);
        progressBar = new JProgressBar(0,100);
            frameProgressBar.add(progressBar, BorderLayout.CENTER);
            frameProgressBar.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            frameProgressBar.setLocationRelativeTo(this);
            frameProgressBar.pack();
            frameProgressBar.setVisible(true);
    }
    
    /**
     * Creates and shows a tabbed dialog box for managing account types and changing the password or
     * database settings.
     */
    private void showOptionsDialog() {
        FrmOptions frmOptions = new FrmOptions(this, lang, prefs, types, setJCEKey, externalSettingsFile);
        if(frmOptions.getResult() == JOptionPane.OK_OPTION) {
            this.types = frmOptions.getTypes();
            this.setJCEKey = frmOptions.getJCEKey();
            loadSettings();
            if(frmOptions.getNeedDBRefresh()) {
               saveDatabase(null,false,true);
            }
            if(frmOptions.getResetDefaultAccounts()) {
                resetDefaultAccounts();
            }
            createMenuToolBars();
            if(setEnableAutoType && System.getProperty("os.name").toLowerCase().startsWith("windows")) {
                if(autoTyper == null) {
                    autoTyper = new AutoTyper(this);
                }
                autoTyper.launchAutoTyper();
            }
            registerAutoTypeHotkey();
            tmrAutoType.setInterval(setAutoLock);
        }
    }
    
    /**
     * Updates the look 'n' feel depending on the user's preference (setLaf).
     */
    private void updateLookNFeel() {
        try {
            if(setLaf.equals("JGoodies")) {
                PlasticXPLookAndFeel.setPlasticTheme(new com.jgoodies.looks.plastic.theme.ExperienceRoyale());
                UIManager.setLookAndFeel(new Plastic3DLookAndFeel());
            } else {
                UIManager.setLookAndFeel(setLaf);
            }
        } catch (UnsupportedLookAndFeelException e) {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch(Exception ex) {
                
            }
        } catch(Exception e) {
            
        }
        Utils.getSafeFont(setLang, true);
        if(setLang.equals("he-IL")) {
            setComponentDirection(ComponentOrientation.RIGHT_TO_LEFT, this);
        }
        SwingUtilities.updateComponentTreeUI(this);
    }

    /**
     * Changes the menu firection to support RTL languages.
     */
    private void setComponentDirection(ComponentOrientation direction, Component comp) {
        this.setComponentOrientation(direction);
        comp.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        if(comp instanceof Container) {
            for(Component subComp : ((Container)comp).getComponents()) {
                setComponentDirection(direction, subComp);
                subComp.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
            }
        }
    }
    
    /**
     * Enables or disables the menu items and buttons.
     * @param state True to enable, false to disable.
     */
    private void setMenuItems(boolean state) {
        mnuAdd.setEnabled(state);
        mnuExport.setEnabled(state);
        mnuImport.setEnabled(state);
        mnuEdit.setEnabled(state);
            itmSearch.setEnabled(state);
            itmRollback.setEnabled(state);
            itmSaveDB.setEnabled(state);
        mnuView.setEnabled(state);
            itmLargeIcons.setEnabled(state);
            itmSmallIcons.setEnabled(state);
            itmSmallIcons.setEnabled(state);
            itmTiles.setEnabled(state);
        btnAdd.setEnabled(state);
        btnRemove.setEnabled(state);
        btnEdit.setEnabled(state);
        btnSearch.setEnabled(state);
        btnViews.setEnabled(state);
        btnRelock.setEnabled(state);
        itmSynchronize.setEnabled(state);
        btnSynchronize.setEnabled(state);
        itmPrint.setEnabled(state);
        btnPrint.setEnabled(state);
        itmRelock.setEnabled(state);
        this.validate();
    }
    
    /**
     * Saves the database, clears all loaded data, then re-prompts for the password.
     */
    public void relock() {
        if(setJCEKey != null && !setJCEKey.equals("")) {
            splitPane.setLeftComponent(null);
            accountView.removeAll();

            saveDatabase(null,false,true);
            setStatusText("");

            promptPass();
            this.validate();
            this.pack();

            setJCEKey = null;
            setMenuItems(false);

            accounts.clear();
            accountGroups.clear();
            types.clear();
            tmrAutoType.stop();
        }
    }
    
    /** 
     * Saves the current preferences and calls saveDatabase()
     */
    private void shutdown() {
        if(autoTyper != null) {
            autoTyper.shutdown();
        }
        if(setJCEKey == null) {
            System.exit(0);
        } else {
            // otherwise the password dialog has been cancelled
            prefs.put("setdbmode", setDBMode);
            prefs.put("setdbhost", setDBHost);
            prefs.put("setdbuser", setDBUser);
            prefs.put("setdbpass", setDBPass);
            prefs.put("setdbname", setDBName);
            prefs.put("setlang", setLang);
            prefs.putInt("setviewmode", (int)setViewMode);
            prefs.putInt("setsortmode", (int)setSortMode);
            prefs.putInt("setsortorder", (int)setSortOrder);
            prefs.putInt("setsplitlocation", splitPane.getDividerLocation());
            prefs.putInt("setsecureclipdelay", setSecureClipDelay);
            prefs.putBoolean("setsecureclipboard", setSecureClipboard);
            prefs.putBoolean("setfirstload", false);
            prefs.putBoolean("setsizemaximized", (this.getExtendedState() == JFrame.MAXIMIZED_BOTH));
            prefs.putInt("setsizewidth", this.getSize().width);
            prefs.putInt("setsizeheight", this.getSize().height);
            prefs.put("setsizeposition", this.getLocation().x+","+this.getLocation().y);
            try {
                prefs.put("setlastexportlocation", setLastExportLocation);
                prefs.put("setlastdblocation", setLastDBLocation);
            } catch(Exception ex) {}
            prefs.put("setlaf", setLaf);
            prefs.putBoolean("setminimizetotray", setMinimizeToTray);
            prefs.putBoolean("setenablefirefox", setEnableFirefox);
            prefs.putBoolean("setenableautotype", setEnableAutoType);
            prefs.put("setautotypehotkey", setAutoTypeHotkey);
            saveDatabase(null,true, true);
        }
    }
    
    /**
     * Prints a list of accounts, types and groups
     */
    private void printdb() {
        // debugging function
        System.out.println("---Accounts---\n");
        for(Account acc: accounts) {
            System.out.print(acc.toString()+" ("+acc.getGroup()+")");
            if(acc.getIsDeleted()) {
                System.out.print(" (Deleted)\n");
            } else {
                System.out.print("\n");
            }
        }
        System.out.println("---Types---\n");
        for(AccType type: types) {
            System.out.print(type.toString());
        }
        System.out.println("---Groups---\n");
        for(Group group: accountGroups) {
            System.out.println(group.toString());
        }
    }

    /**
     * Resets the default list of account types
     */
    private void resetDefaultTypes() {
        LinkedHashMap<String, String[]> typeMap = new LinkedHashMap<String,String[]>();
        typeMap.put(lang.getString("BLANK"), new String[] {});
        typeMap.put(lang.getString("CONTACT"), new String[] {"First Name", "Last Name", "House Number", "Line 1", "Line 2","Line 3", "City", "State/Province", "Post Code/ZIP", "Country", "Email Address", "Phone" });
        typeMap.put(lang.getString("BANKACCOUNT"), new String[] {"Bank","Branch", "Sort Code", "Account Number", "PIN", "If Lost", "Phone", "IBAN"});
        typeMap.put(lang.getString("COMBINATIONLOCK"), new String[] {"Location", "Code"});
        typeMap.put(lang.getString("CREDITCARD"),new String[] {"Card Number", "First Name", "Last Name", "Issue", "Start Date", "Expiry Date", "PIN", "CCV", "If Lost"});
        typeMap.put(lang.getString("DRIVINGLICENSE"), new String[] {"Name", "Valid from", "Valid to", "Issuing Authority","License Number", "Category"});
        typeMap.put(lang.getString("MEMBERSHIP"), new String[] {"Company", "Membership number", "Expires", "Phone"});
        typeMap.put(lang.getString("EMAILACC"), new String[] {"Login", "Password", "POP3", "SMTP", "IMAP"});
        typeMap.put(lang.getString("PRESCRIPTION"), new String[] {"Drug", "Dosage", "Frequency", "Brand", "Doctor", "Pharmacy", "Phone"});
        typeMap.put("MasterCard", new String[] {"Card Number", "First Name", "Last Name", "Issue", "Start Date", "Expiry Date", "PIN", "CCV", "If Lost"});
        typeMap.put("Maestro", new String[] {"Card Number", "First Name", "Last Name", "Issue", "Expiry Date", "PIN", "CCV", "If Lost"});
        typeMap.put(lang.getString("SERIAL"), new String[] {"Company", "Serial Number", "Purchased", "Email"});
        typeMap.put("VISA", new String[] {"Card Number", "First Name", "Last Name", "Issue", "Start Date", "Expiry Date", "PIN", "CCV", "If Lost"});
        
        for(Object typeName : typeMap.keySet()) {
            if(Utils.getTypeIndex(typeName.toString(),types,"types") < 0) {
                types.add(new AccType(typeName.toString(), (String[])typeMap.get(typeName), true, false, true));
            }
        }
        addWebsiteType();
    }

    /**
      * Populates the database with dummy data
      */
    private void resetDefaultAccounts()  {
        String[] groupNames = {"Financial", "Other", "Membership"};
        accountGroups.clear();
        for(String name : groupNames) {
            accountGroups.add(new Group(name, "LockCrypt", true));
        }
        LinkedHashMap<String, String[]> accountMap = new LinkedHashMap<String,String[]>();
        accountMap.put("Natwest Current", new String[] {lang.getString("BANKACCOUNT") ,"creditcard","Financial","Natwest","London","37-09-28", "98998998","1234","08451 404 404","08451 404 404",""});
        accountMap.put("Harold Zoid", new String[] {lang.getString("CONTACT"),"user_office","Other", "Harold", "Zoid", "54", "Las Vegas Blvd.", "","","New New York", "New York", "NNY 40939", "USA", "h.zoid@lobstertainment.com", "1-555-3227"});
        accountMap.put("Garage Lock", new String[] {lang.getString("COMBINATIONLOCK"), "padlock","Other", "Parking lot B", "55678"});
        accountMap.put("First Direct Maestro", new String[] {"Maestro", "creditcard","Financial", "675999 999999999999", "An", "Other", "1", "02/2010", "4567", "771", "0800 123 456"});
        accountMap.put("MBNA MasterCard", new String[] {lang.getString("MASTERCARD"), "creditcard","Financial", "675999 999999999999", "An", "Other", "1", "01/2008", "02/2010", "4567", "771", "0800 123 456"});
        accountMap.put("Gym Membership Card", new String[] {lang.getString("MEMBERSHIP"), "home", "Membership","Fitness First", "FF13579246", "01/2010", "01632 772 767"});
        accountMap.put("Headache Tablets", new String[] {lang.getString("PRESCRIPTION"), "medicine","Other", "Sodium Benzoate","3 tablets","Twice daily", "Acme", "Dr Scratchansniff", "Acme Pharmacy", "555-3227"});
        accountMap.put("Windows Serial", new String[] {lang.getString("SERIAL"), "pirate","Other","Microsoft","FCKGW-N0S3R-1ALS0-RRYM4-D3UP0", "02/2006", "support@m6.com"});
        accountMap.put("Iñtërnâtiônàlizætiøn Support", new String[] {lang.getString("SERIAL"), "flag_blue","Other","ÄäáéÖöőóÜüß","български","Русский", ""});
        
        long curDate = new java.util.Date().getTime();
        for(Object accNameObj : accountMap.keySet()) {
            String accName = (String)accNameObj;
            String[] fieldVals = (String[])accountMap.get(accNameObj);
            if(Utils.getTreeIndex(accName, accountTree) < 0) { // returns -1 if account doesn't exist
                //Info: indexes of fieldVals above 0 are fields, 0 is the type
                if(Utils.getArrayListIndex(accName, accounts) >= 0) { // does not exist in the list, but has been deleted since the database has been synchronised, so still exists in 'accounts' arraylist
                    accounts.remove(Utils.getArrayListIndex(accName,accounts));
                }
                if(Utils.getTypeIndex(fieldVals[0], types, "list") >= 0) {
                    AccType type = types.get(Utils.getTypeIndex(fieldVals[0], types, "types")); 
                    String[] fieldNames = type.getFields();
                    LinkedHashMap<String,String> tempMap = new LinkedHashMap<String,String>();
                    for(int i=0;i<fieldNames.length;i++) {
                        tempMap.put(fieldNames[i], fieldVals[i+3]);
                    }
                    accounts.add(new Account(accName, fieldVals[0], fieldVals[1], new AccType(fieldVals[0], tempMap, true, ""), true,fieldVals[2], curDate, curDate));
                } else {
                    if(DEBUG) {
                        System.out.println("Type not found: '"+fieldVals[0]+"'");
                    }
                }
            } else {
                if(DEBUG) {
                    System.out.println("Account already exists with the name '"+accName+"'");
                }
            }
        }
        refreshAccountTree();
    }
    
    /**
     * Adds the website type (used by the Firefox plugin) if it does not already exist.
     */
    private void addWebsiteType() {
        if(Utils.getTypeIndex(lang.getString("WEBSITE").toString(),types,"list") < 0) {
            // ~}* marks a field as a password, ~}! makes it hidden, ~}u marks it as a URL
            AccType websiteType = new AccType(lang.getString("WEBSITE"), new String[] { "~}uURL", "Username", "~}*Password", "Username Field", "Password Field", "Registered Email" }, true, true, true);
            websiteType.setID("__pass");
            types.add(websiteType);
        }
    }
    
    /**
     * Sets the menu buttons and shows the selected group or account
     * @param node
     */
    public void processNode(DefaultMutableTreeNode node) {
        if(node instanceof Account) {
            Account accountThisNode = (Account)node;
            if((selectedAccount != null) && (accountThisNode != null) && !selectedAccount.equals(accountThisNode)) {
                setSelectedAccount(accountThisNode);
            }
            setMenuButtons(1);
        } else if(node instanceof Group) {
            setMenuButtons(2);
            setSelectedGroup((Group)node);
        } else if(node.isRoot()) {
            setMenuButtons(0);
            setSelectedGroup((Group)node);
        }
    }

    /**
     * Processes a mouse click on a JTable and shows either the clicked account or a popupmenu.
     * Used by the searchResults table and groupTable.
     * @param e The MouseEvent captured by the listener
     * @param table The JTable for which to process the event
     */
    private void processPopupTrigger(MouseEvent e, JTable table) {
        Point point = e.getPoint();
        table.changeSelection(table.rowAtPoint(point), table.columnAtPoint(point), false, false);
        Account acc = null;
        Object selectedVal = table.getValueAt(table.getSelectedRow(), table.getSelectedColumn());
        if(selectedVal instanceof Account) {
             acc = (Account)selectedVal;
        } else if(setViewMode == 'I') {
            String accName = (String)table.getValueAt(table.rowAtPoint(point), 1);
            if(accName != null && !accName.isEmpty()) {
                // if this is list view, the account data is spread across different columns, so get the name of the account (col #1), then use getAccountIndex()
                acc = accounts.get(Utils.getArrayListIndex(accName, accounts));
            }
        }
        final Account account = acc;
        if(account == null) {
            return; //nothing was selected
        } else {
            if(e.getButton() == MouseEvent.BUTTON1) {
                if(e.getClickCount() == 2) {
                    setSelectedAccount(account);
                    this.requestFocus();
                }
            } else {
                if(SwingUtilities.isRightMouseButton(e) && !accountTree.isSelectionEmpty()) {
                    ImageIcon delIcon = new ImageIcon(this.getClass().getResource("images/delete_16.png"), lang.getString("DELETE"));
                    ImageIcon editIcon = new ImageIcon(this.getClass().getResource("images/edit_16.png"), lang.getString("EDIT"));
                    ImageIcon createCopyIcon = new ImageIcon(this.getClass().getResource("images/copy_16.png"), lang.getString("CREATECOPY"));
                    ImageIcon autoTypeIcon = new ImageIcon(this.getClass().getResource("images/autotype_16.png"), lang.getString("AUTOTYPE"));
                    JPopupMenu groupViewContextMenu = new JPopupMenu();
                        JMenuItem itmCtxAutoType = new JMenuItem(lang.getString("AUTOTYPE"), autoTypeIcon);
                            groupViewContextMenu.add(itmCtxAutoType);
                            itmCtxAutoType.addActionListener(new ActionListener() {
                               public void actionPerformed(ActionEvent e) { doAutoType(account); }
                            });
                        JMenuItem itmCtxCreateCopy = new JMenuItem(lang.getString("CREATECOPY"), createCopyIcon);
                            groupViewContextMenu.add(itmCtxCreateCopy);
                            itmCtxCreateCopy.addActionListener(new ActionListener() {
                               public void actionPerformed(ActionEvent e) { createCopy(account); }
                            });
                        JMenuItem itmCtxEditAccount = new JMenuItem(lang.getString("EDITACCOUNT"), editIcon);
                            groupViewContextMenu.add(itmCtxEditAccount);
                            itmCtxEditAccount.addActionListener(new ActionListener() {
                               public void actionPerformed(ActionEvent e) { showAddDialog(account, true); } 
                            });
                        JMenuItem itmCtxDelete = new JMenuItem(lang.getString("DELACCOUNT"), delIcon);
                            groupViewContextMenu.add(itmCtxDelete);
                            itmCtxDelete.addActionListener(new ActionListener() {
                               public void actionPerformed(ActionEvent e) { removeAccount(account); } 
                            });
                        
                    groupViewContextMenu.show(table, e.getX(),e.getY());
                }
            }
        }
    }

    /**
     * Changes the current view to large/small icons, list or tiles
     */
    private void changeView() {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)accountTree.getLastSelectedPathComponent();
        if(!(node instanceof Account)) {
            setSelectedGroup((Group)node);
        }
    }
    
    /**
     * Starts a server to listen for connections on the loopback interface.
     * Necessary for the LockCrypt Firefox extension.
     */
    private void startFirefoxServer() {
        stopFirefoxServer();
        try {
            firefoxServerThread = new FirefoxSocket(this);
            firefoxServerThread.start();
        } catch(Exception ex) { 
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#222: "+lang.getString("ERR_STARTFIREFOXSERVER"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }
    private void stopFirefoxServer() {
        try {
            if(firefoxServerThread != null) {
                firefoxServerThread.stopServer();
                firefoxServerThread.join(1000);
            }
        } catch(Exception ex) { 
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#223: "+lang.getString("ERR_STOPFIREFOXSERVER"), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Finds accounts of type 'Website' which match the given url.
     * @param URL The URL to match
     * @return a JSON string containing matches
     */
    public String findPasswords(String url) {
        JSONObject jsoMatches = new JSONObject();
        try {
            if(setJCEKey == null) {
                jsoMatches.put("error", "Not logged in to LockCrypt");
            } else {
                JSONArray jsaAccounts = new JSONArray(),
                          jsaFields;
                JSONObject jsoCurrentAccount,
                           jsoCurrentField;
                String accountURL;
                URL currentURL = new URL(url);
                for(Account account : accounts) {
                    if(!account.getIsDeleted() && account.getTypeID().equals("__pass")) {
                        accountURL = account.getField("URL");
                        if(accountURL.length() == 0 || new WildcardURL(accountURL).matches(currentURL)) {
                            jsoCurrentAccount = new JSONObject();
                            jsoCurrentAccount.put("name", URLEncoder.encode(account.getName(), "utf8").replace('+', ' '));
                            if(account.fieldExists("Username Field")) { // account has either been saved automatically or exported from Firefox
                                jsoCurrentAccount.put("username", URLEncoder.encode(account.getField("Username"), "utf8").replace('+', ' '));
                                jsoCurrentAccount.put("usernameID", URLEncoder.encode(account.getField("Username Field"), "utf8").replace('+', ' '));
                                jsoCurrentAccount.put("password", URLEncoder.encode(account.getField("Password"), "utf8").replace('+', ' '));
                                jsoCurrentAccount.put("passwordID", URLEncoder.encode(account.getField("Password Field"), "utf8").replace('+', ' '));
                                System.out.print("");
                            } else {
                                jsaFields = new JSONArray();
                                for(String fieldname : account.getTypeObject().getFieldMap().keySet()) {
                                    jsoCurrentField = new JSONObject();
                                    jsoCurrentField.put("name", URLEncoder.encode(fieldname.startsWith("~}") ? fieldname.substring(3) : fieldname, "utf8").replace('+', ' '));
                                    jsoCurrentField.put("value", URLEncoder.encode(account.getField(fieldname), "utf8").replace('+', ' '));
                                    jsaFields.put(jsoCurrentField);
                                }
                                jsoCurrentAccount.put("fields", jsaFields);
                            }
                            jsaAccounts.put(jsoCurrentAccount);
                        }
                    }
                }
                jsoMatches.put("matches", jsaAccounts);
            }
        } catch(Exception ex) {
            System.out.println(ex.getMessage());
            try {
                jsoMatches.put("error", "Could not parse account");
            } catch(Exception ignore) {}
        }
        return jsoMatches.toString();
    }

    /**
     * Checks to see if an exact match for an account exists
     * @param jso A JSON object containing the fields in the Firefox form
     * @return A JSON object with an 'exists' or 'error' property
     */
    public String checkAccountExists(JSONObject jso) {
        JSONObject jsoResponse = new JSONObject();
        try {
            if(setJCEKey == null) {
                jsoResponse.put("error", "Not logged in to LockCrypt");
            } else {
                boolean exists = false;
                String url = Utils.stripURL(jso.getString("URL"), false);
                JSONArray formFields = jso.optJSONArray("fields");
                JSONObject currentFormField;
                String fieldName,
                       fieldValue;
                LinkedHashMap<String,String> existingFields;
                for(Account account : accounts) {
                    existingFields = account.getFields();
                    try {
                        if(!account.getIsDeleted() && account.getTypeID().equals("__pass") && account.getField("URL").contains(url)) { // account exists for this URL
                            if(formFields.length() != existingFields.size()-1) { //one field foer the URL, if the field count is different, they can't be identical
                                continue;
                            }
                            boolean accountIdentical = true;
                            for(int i=0;i<formFields.length();i++) { // check to see if there's a field in the form which is different from the saved account
                                currentFormField = formFields.optJSONObject(i);
                                fieldName = URLDecoder.decode(currentFormField.getString("name").trim(), "utf8");
                                fieldValue = URLDecoder.decode(currentFormField.getString("value"), "utf8");
                                if(account.fieldExists(fieldName) && !account.getField(fieldName).equals(fieldValue)) {
                                    accountIdentical = false;
                                }
                            }
                            if(accountIdentical) {
                                exists = true;
                                break;
                            }
                        }
                    } catch(Exception ex) {
                        jsoResponse.put("error", ex.getMessage());
                    }
                }
                jsoResponse.put("exists", exists);
            }
        } catch(Exception ex) {
            try {
                jsoResponse.put("error", ex.getMessage());
            } catch(Exception ignore) { }
        }
        return jsoResponse.toString();
    }

    /**
     * Finds the account names which apply to the given URL
     * @param url The url to match
     * @return a JSON object of the matches and a suggested name for the new account - with keys for "error" (string), "existing" (array) and suggested (string)
     */
    public String findPasswordNames(String url) {
        JSONObject jso = new JSONObject();
        try {
            URL currentURL = new URL(url);
            if(setJCEKey == null) {
                jso.put("error", "Not logged in to LockCrypt");
            } else {
                jso.put("suggested", URLEncoder.encode(Utils.getNextAvailableAccountName(Utils.stripURL(url, true), accounts), "utf8").replace('+', ' '));
                JSONArray existingNames = new JSONArray();
                String accountURL;
                for(Account account : accounts) {
                    try {
                        if(!account.getIsDeleted() && account.getTypeID().equals("__pass")) {
                            accountURL = account.getField("URL");
                            if(accountURL.length() == 0 || new WildcardURL(accountURL).matches(currentURL)) {
                                existingNames.put(URLEncoder.encode(account.getName(), "utf8").replace('+', ' '));
                            }
                        }
                    } catch(Exception ignore) {}
                }
                jso.put("existing", existingNames);
            }
        } catch(Exception ignore) {
            ignore.printStackTrace();
        }
        return jso.toString();
    }
    
    /**
     * Saves a username/password combination for an account
     * @param jso The JSON object containing account data.
     * @return A string containing errors, if there were any
     */
    public String savePassword(JSONObject jso) {
        try {
            String name = URLDecoder.decode(jso.getString("Name"), "utf8");
            int index = Utils.getArrayListIndex(name, accounts);
            if(index >= 0) {
                Account oldAccount = accounts.get(index);
                getNodeFromName(accountTreeRoot, oldAccount).removeFromParent();
                oldAccount.delete();
            }
            LinkedHashMap<String, String> fields = new LinkedHashMap<String, String>();
            fields.put("~}uURL", Utils.stripURL(jso.getString("URL"), false));
            JSONArray formFields = jso.optJSONArray("Fields");
            JSONObject currentFormField;
            String fieldName,
                   fieldValue;
            for(int i=0;i<formFields.length();i++) {
                currentFormField = formFields.optJSONObject(i);
                if(currentFormField.getBoolean("include")) {
                    fieldName = URLDecoder.decode(currentFormField.getString("name").trim(), "utf8");
                    if(fieldName.indexOf("pass") >= 0) {
                        fieldName = "~}*" + fieldName;
                    }
                    fieldValue = URLDecoder.decode(currentFormField.getString("value"), "utf8");
                    fields.put(fieldName.endsWith(":") ? fieldName.substring(0, fieldName.length()-1) : fieldName, fieldValue);
                }
            }
            AccType accType = new AccType(lang.getString("WEBSITE"), fields, true, "");
            accType.setID("__pass");
            java.util.Date now = new java.util.Date();
            Account newAccount = new Account(name, 
                                             lang.getString("WEBSITE"), 
                                             "network", 
                                             accType, 
                                             true, 
                                             "LockCrypt", 
                                             now.getTime(),
                                             now.getTime());
            addAccount(newAccount);
            return "";
        } catch(Exception ex) {
            return ex.getMessage();
        }
    }
    
    /**
     * Imports passwords from Firefox
     * @param data The JSON string from Firefox
     * @return A JSON object, contaning "error" or "response"
     */
    public String importFromFirefox(String data) {
        StringBuffer errors = new StringBuffer();
        JSONObject jsoResponse = new JSONObject();
        int numExported = 0;
        try {
            if(setJCEKey == null) {
                errors.append("Not logged in to LockCrypt");
            } else {
                JSONArray jsa = new JSONArray(data);
                JSONObject jsoAccount;
                Account newAccount;
                AccType accType;
                LinkedHashMap<String, String> fields;
                String url, username;
                java.util.Date now = new java.util.Date();
                if(Utils.getArrayListIndex("Firefox Passwords", accountGroups) < 0) {
                    addGroup(new Group("Firefox Passwords", "LockCrypt", false));
                }
                for(int i=0;i<jsa.length();i++) {
                    jsoAccount = jsa.getJSONObject(i);
                    url = URLDecoder.decode(jsoAccount.getString("url"), "utf8");
                    username = URLDecoder.decode(jsoAccount.getString("username"), "utf8");
                    boolean accountExists = false;
                    for(Account acc : accounts) {
                        if(!acc.getIsDeleted() && acc.getTypeID().equals("__pass") && url.equals(acc.getField("URL"))) {
                            String accUsername = acc.getField("Username");
                            if(accUsername != null && accUsername.equals(username)) {
                                accountExists = true;
                                break;
                            }
                        }
                    }
                    if(!accountExists) {
                        fields = new LinkedHashMap<String, String>();
                        fields.put("~}uURL", url);
                        fields.put("Username", username);
                        fields.put("~}*Password", URLDecoder.decode(jsoAccount.getString("password"), "utf8"));
                        fields.put("Username Field", URLDecoder.decode(jsoAccount.getString("usernameID"), "utf8"));
                        fields.put("Password Field", URLDecoder.decode(jsoAccount.getString("passwordID"), "utf8"));
                        accType = new AccType(lang.getString("WEBSITE"), fields, true, "");
                        accType.setID("__pass");
                        newAccount = new Account(Utils.getNextAvailableAccountName(Utils.stripURL(url, true), accounts), 
                                                 lang.getString("WEBSITE"), 
                                                 "network", 
                                                 accType, 
                                                 true, 
                                                 "Firefox Passwords", 
                                                 now.getTime(),
                                                 now.getTime());
                        accounts.add(newAccount);
                        numExported++;
                    }
                }
                MessageFormat formatter = new MessageFormat(lang.getString("NUMEXPORTED"));
                jsoResponse.put("response",URLEncoder.encode(formatter.format(new Object[] {numExported}), "utf8").replace('+', ' '));
            }
        } catch(Exception ex) {
            errors.append(ex+"\n");
        }
        if(errors.length() > 0) {
            try {
                jsoResponse.put("error", errors.toString());
            } catch(Exception ignore) {}
        }
        refreshAccountTree();
        return jsoResponse.toString();
    }
    
    /**
     * Exports all accounts of type "Website" to Firefox
     * @return A JSON string containing account data
     */
    public String exportToFirefox() {
        StringBuffer errors = new StringBuffer();
        JSONObject jsoResponse = new JSONObject();
        try {
            if(setJCEKey == null) {
                errors.append("Not logged in to LockCrypt");
            } else {
                JSONArray jsaAccounts = new JSONArray();
                JSONObject jsoCurrentAccount;
                String hostname;
                for(Account account : accounts) {
                    if(!account.getIsDeleted() && account.getTypeID().equals("__pass")) {
                        jsoCurrentAccount = new JSONObject();
                        hostname = Utils.stripURL(account.getField("URL"), false);
                        if(hostname.length() > 10) { // more tha http://a.b
                            jsoCurrentAccount.put("hostname", URLEncoder.encode(hostname, "utf8").replace('+', ' '));
                            jsoCurrentAccount.put("username", URLEncoder.encode(account.getField("Username"), "utf8").replace('+', ' '));
                            jsoCurrentAccount.put("password", URLEncoder.encode(account.getField("Password"), "utf8").replace('+', ' '));
                            jsoCurrentAccount.put("usernameField", URLEncoder.encode(account.getField("Username Field"), "utf8").replace('+', ' '));
                            jsoCurrentAccount.put("passwordField", URLEncoder.encode(account.getField("Password Field"), "utf8").replace('+', ' '));
                            jsaAccounts.put(jsoCurrentAccount);
                        }
                    }
                }
                jsoResponse.put("accounts", jsaAccounts);
            }
        } catch(Exception ex) {
            errors.append(ex+"\n");
        }
        if(errors.length() > 0) {
            try {
                jsoResponse.put("error", URLEncoder.encode(errors.toString(), "utf8").replace('+', ' '));
            } catch(Exception ignore) {}
        }
        return jsoResponse.toString();
    }
    
    public boolean getDebug() {
        return this.DEBUG;
    }
    
    /**
     * Updates the progress bar to show changes in completion.
     */
    class ProgressListener implements PropertyChangeListener {
         public void propertyChange(PropertyChangeEvent e) {
            if(e.getPropertyName().equals("progress")) {
                int progress = (Integer)e.getNewValue();
                progressBar.setValue(progress);
            }
        }
    }
    
    /**
     * Renders account objects when a group is selected
     */
    class GroupIconRenderer extends DefaultTableCellRenderer implements TableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(
                        JTable table, Object obj,
                        boolean isSelected, boolean hasFocus,
                        int row, int column) {
            if(obj instanceof Account) {
                Account account = (Account)obj;
                if(account.getName().equals("")) {
                    return null;
                } else {
                    GroupTableModel tableModel = (GroupTableModel)table.getModel();
                    int colWidth = tableModel.getColWidth();
                    int rowHeight = tableModel.getRowHeight();
                    int iconSize = tableModel.getIconSize();
                    ImageIcon imgIcon = null;
                    try {
                        URL iconURL = this.getClass().getResource("images/types/"+account.getIcon()+"_"+iconSize+".png");
                        imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                        this.setIcon(imgIcon);
                        if(isSelected) {
                            this.setBackground(Color.getHSBColor(0.58f, 0.2f, 1f));
                        } else {
                            this.setBackground(Color.WHITE);
                        }
                    } catch(Exception ignore) {/**/}
                    if(setViewMode == 'T') { //tile view
                        JPanel thisLabel = new JPanel(new FlowLayout(FlowLayout.LEFT));
                            thisLabel.add(new JLabel(imgIcon));
                        JPanel textPart = new JPanel();
                            thisLabel.add(textPart);
                            textPart.setBackground(null);
                            textPart.setLayout(new BoxLayout(textPart, BoxLayout.PAGE_AXIS));
                            String accName = account.getName();
                            JLabel lblName = new JLabel(accName);
                            lblName.getGraphics();
                                if(lblName.getFontMetrics(lblName.getFont()).stringWidth(accName) > 120) {
                                    lblName.setText(accName.substring(0,15)+"...");
                                }
                                textPart.add(lblName);
                            textPart.add(new JLabel(account.getType()));
                        thisLabel.setMinimumSize(new Dimension(colWidth,rowHeight));
                        if(isSelected) {
                            thisLabel.setBackground(Color.getHSBColor(0.58f, 0.2f, 1f));
                        } else {
                            thisLabel.setBackground(Color.WHITE);
                        }
                        return thisLabel;
                    } else { // icon view
                        this.setVerticalTextPosition(SwingConstants.BOTTOM);
                        this.setHorizontalTextPosition(SwingConstants.CENTER);
                        this.setHorizontalAlignment(SwingConstants.CENTER);
                        this.setVerticalAlignment(SwingConstants.CENTER);
                        this.setMinimumSize(new Dimension(colWidth,rowHeight));
                        this.setText(account.getName());
                        this.setToolTipText(account.getName());
                        if(isSelected) {
                            this.setBackground(Color.getHSBColor(0.58f, 0.2f, 1f));
                        } else {
                            this.setBackground(Color.WHITE);
                        }
                        return this;
                    }
                } 
            } else {
                return null;
            }
        }
    }
    
    /**
     * Renders the account tree, using icons for accounts and folder images for groups.
     */
    class AccountTreeRenderer extends DefaultTreeCellRenderer {
        @Override
        public Component getTreeCellRendererComponent(JTree t,Object value,
                                                      boolean sel,boolean expanded,
                                                      boolean leaf,int row,boolean hasFocus) {
            DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)super.getTreeCellRendererComponent(t, value, sel, expanded, leaf, row, hasFocus);
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
            URL iconURL;
            if(node.isLeaf()) {
                try {
                    Account account = (Account)node;
                    iconURL = this.getClass().getResource("images/types/"+account.getIcon()+"_16.png");
                    ImageIcon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                    renderer.setIcon(imgIcon);
                    renderer.setText(account.getName());
                } catch(Exception e) {
                    try { //this is used for childless groups
                        if(expanded) {
                            iconURL = this.getClass().getResource("images/types/folder_open_16.png");
                        } else {
                            iconURL = this.getClass().getResource("images/types/folder_closed_16.png");
                        }
                        ImageIcon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                        renderer.setIcon(imgIcon);
                    } catch(Exception ex) { /*ignore*/ }
                }
            } else {
                 try { //used for groups which have children
                    if(expanded) {
                        iconURL = this.getClass().getResource("images/types/folder_open_16.png");
                    } else {
                        iconURL = this.getClass().getResource("images/types/folder_closed_16.png");
                    }
                    ImageIcon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                    renderer.setIcon(imgIcon);
                } catch(Exception ex) { /*ignore*/ }
            }
            return renderer;
        }
    }
    
    /**
     * Renders search results in a JTable
     */
    class SearchResultsRenderer extends DefaultTableCellRenderer implements TableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(
                        JTable table, Object acc,
                        boolean isSelected, boolean hasFocus,
                        int row, int column) {
            Component cell = super.getTableCellRendererComponent(
                     table, acc, isSelected, hasFocus, row, column);
            if(acc == null) {
                return null;
            } else {
                this.setVerticalTextPosition(SwingConstants.BOTTOM);
                this.setHorizontalTextPosition(SwingConstants.CENTER);
                this.setHorizontalAlignment(SwingConstants.CENTER);
                this.setVerticalAlignment(SwingConstants.CENTER);
                this.setForeground(Color.BLACK);
                try {
                    Account account = (Account)acc;
                    URL iconURL = this.getClass().getResource("images/types/"+account.getIcon()+"_64.png");
                    ImageIcon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                    this.setIcon(imgIcon);
                } catch(Exception e) {

                }
                if(isSelected) {
                    this.setBackground(Color.getHSBColor(0.58f, 0.2f, 1f));
                } else {
                    this.setBackground(Color.WHITE);
                }
            }
            return this;
        }
    }

    /**
     * Listens for mouse clicks on search results and shows context menus.
     */
    class SearchResultsMouseListener extends MouseAdapter {
        @Override
        public void mouseClicked(MouseEvent e) {
            processPopupTrigger(e, searchResults);
        }
    }
    
    /**
     * Listens for clicks on the AccountTree and changes the selected node, shows the edit dialog
     */
    class AccountTreeMouseListener extends MouseAdapter {
        @Override
        public void mouseClicked(MouseEvent e) {
            TreePath selectedPath = accountTree.getPathForLocation(e.getX(), e.getY());
            if(selectedPath == null) {
                accountTree.setSelectionPath(accountTree.getPathForRow(0)); //select root node
            } else {
                accountTree.setSelectionPath(selectedPath);
            }
            this.processPopupTrigger(e);
        }
        private void processPopupTrigger(MouseEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)accountTree.getLastSelectedPathComponent();
            if(node == null || setJCEKey == null) {
                return; //nothing was selected
            } else {
                if(node instanceof Account) {
                    try {
                        Account account = (Account)node;
                        if(e.getButton() == MouseEvent.BUTTON1) {
                            if(e.getClickCount() == 2) {
                                selectedAccount = account;
                                showAddDialog(account, true);
                            } else {
                                setSelectedAccount((Account)node);
                            }
                        } else {
                            if(SwingUtilities.isRightMouseButton(e) && !accountTree.isSelectionEmpty()) {
                                accountTreePopup = createPopupMenu();
                                accountTreePopup.show(accountTree, e.getX(),e.getY());
                            }
                            setSelectedAccount((Account)node);
                        }
                    } catch(Exception ignore) {}
                } else { // it's a group
                    Group group;
                    if(node.isRoot()) {
                        group = (Group)node;
                    } else {
                        group = (Group)node;
                    }
                    try {
                        if(SwingUtilities.isRightMouseButton(e) && !accountTree.isSelectionEmpty()) {
                            accountTreePopup = createPopupMenu();
                            accountTreePopup.show(accountTree, e.getX(),e.getY());
                        }
                    } catch(Exception ignore) {}
                    setSelectedGroup(group);
                }
            }
        }
    }
    
    /**
     * Processes mouse clicks when an account is clicked in Group view.
     */
    class GroupViewListener extends MouseAdapter {
        @Override
        public void mouseClicked(MouseEvent e) {
            processPopupTrigger(e, groupTable);
        }
    }
    
    /**
     * Processes clicks on the main toolbar.
     */
    class ToolbarListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            if(cmd.equals("add")) {
                showAddDialog(new Account(), false);
            } else if(cmd.equals("remove")) {
                DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)accountTree.getLastSelectedPathComponent();
                Object selectedObj = selectedNode;
                if(selectedObj instanceof Account) {
                    removeAccount((Account)selectedObj);
                } else if(selectedObj instanceof Group) {
                    removeGroup((Group)selectedObj,true);
                } else {
                    if(DEBUG) {
                        System.out.println("Type not recognized");
                    }
                }
            } else if(cmd.equals("edit")) {
                Object selectedObj = ((DefaultMutableTreeNode)accountTree.getLastSelectedPathComponent());
                if(selectedObj instanceof Account) {
                    showAddDialog((Account)selectedObj, true);
                } else if(selectedObj instanceof Group) {
                    showAddGroupDialog((Group)selectedObj, true);
                }
            } else if(cmd.equals("settings")) {
                showOptionsDialog();
            } else if(cmd.equals("search")) {
                showSearchDialog();
            } else if(cmd.equals("pwgen")) {
                FrmPWGen frmPWGen = new FrmPWGen(mainWindow, lang);
                Point parentLoc = mainWindow.getLocation();
                frmPWGen.setLocation(parentLoc.x + ((mainWindow.getWidth() - frmPWGen.getWidth())/2), parentLoc.y + ((mainWindow.getHeight() - frmPWGen.getHeight())/2));
                frmPWGen.setVisible(true);
            } else if(cmd.equals("view")) {
                switch(setViewMode) {
                    case 'L':
                        setViewMode = 'S';
                        break;
                    case 'S':
                        setViewMode = 'I';
                        break;
                    case 'I':
                        setViewMode = 'T';
                        break;
                    case 'T':
                        setViewMode = 'L';
                        break;
                }
                if(DEBUG) {
                    System.out.println("Changed view: "+setViewMode);
                }
                changeView();
            } else if(cmd.equals("print")) {
                showPrintDialog();
            } else if(cmd.equals("relock")) {
                relock();
            } else if(cmd.equals("synchronize")) {
                launchSyncUtility();
            }
        }
    }
    
    /**
     * Model for the account table.
     */
    class AccountTableModel extends AbstractTableModel {
        private Account[][] data;
        public AccountTableModel(Account[][] data) {
            this.data = data;
        }
        public int getColumnCount() {
            return data[0].length;
        }
        public int getRowCount() {
            return data.length;
        }
        public Account getValueAt(int row, int col) {
            return data[row][col];
        }
    }
    
    /**
     * Compares accounts by name
     */
    class AccountComparator implements Comparator<Account> {
        public int compare(Account someAccount, Account otherAccount) {
            Account a1 = someAccount;
            Account a2 = otherAccount;
            return a1.getName().compareTo(a2.getName());
        }
    }

    class ViewModeListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            if(itmLargeIcons.isSelected()) {
                setViewMode = 'L';
            } else if(itmSmallIcons.isSelected()) {
                setViewMode = 'S';
            } else if(itmList.isSelected()) {
                setViewMode = 'I';
            } else if(itmTiles.isSelected()) {
                setViewMode = 'T';
            }
            changeView();
        }
    }
    
    private void setTreeComparator() {
        if(setSortMode == 'N') {
            treeComparator = new Comparator<AccountTreeNode>() {
                public int compare(AccountTreeNode acc1, AccountTreeNode acc2) {
                    int sortOrder = acc1.getName().compareToIgnoreCase(acc2.getName());
                    if(setSortOrder == 'A') {
                        return sortOrder;
                    } else {
                        if(sortOrder > 0) {
                            return -1;
                        } else if(sortOrder < 0) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                }
            };
        } else if(setSortMode == 'T') {
            treeComparator = new Comparator<AccountTreeNode>() {
                public int compare(AccountTreeNode obj1, AccountTreeNode obj2) {
                    String obj1Type = null, obj2Type = null;
                    if(obj1 instanceof Account) {
                        obj1Type = ((Account)obj1).getType();
                    } else {
                        obj1Type = "AAAAAAAAAAAAAA";
                    }
                    if(obj2 instanceof Account) {
                        obj2Type = ((Account)obj2).getType();
                    } else {
                        obj2Type = "AAAAAAAAAAAAAA";
                    }
                    int sortOrder = obj1Type.compareToIgnoreCase(obj2Type);
                    if(setSortOrder == 'A') {
                        // Sort by name if types are the same
                        if(obj1Type.equals(obj2Type)) {
                            return ((AccountTreeNode)obj1).getName().compareToIgnoreCase(((AccountTreeNode)obj2).getName());
                        } else {
                            return sortOrder;
                        }
                    } else {
                        if(sortOrder > 0) {
                            return -1;
                        } else if(sortOrder < 0) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                }
            };
        } else if(setSortMode == 'C') {
            treeComparator = new Comparator<AccountTreeNode>() {
                public int compare(AccountTreeNode acc1, AccountTreeNode acc2) {
                    java.util.Date obj1Created = acc1.getDateCreated(), 
                                   obj2Created = acc2.getDateCreated();
                    int sortOrder = obj1Created.compareTo(obj2Created);
                    if(setSortOrder == 'A') {
                        // Sort by name if dates are the same
                        if(obj1Created.equals(obj2Created)) {
                            return acc1.getName().compareToIgnoreCase(acc2.getName());
                        } else {
                            return sortOrder;
                        }
                    } else {
                        if(sortOrder > 0) {
                            return -1;
                        } else if(sortOrder < 0) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                }
            };
        } else if(setSortMode == 'M') {
            treeComparator = new Comparator<AccountTreeNode>() {
                public int compare(AccountTreeNode acc1, AccountTreeNode acc2) {
                    java.util.Date obj1Created = acc1.getDateModified(), 
                                   obj2Created = acc2.getDateModified();
                    int sortOrder = obj1Created.compareTo(obj2Created);
                    if(setSortOrder == 'A') {
                        // Sort by name if dates are the same
                        if(obj1Created.equals(obj2Created)) {
                            return acc1.getName().compareToIgnoreCase(acc2.getName());
                        } else {
                            return sortOrder;
                        }
                    } else {
                        if(sortOrder > 0) {
                            return -1;
                        } else if(sortOrder < 0) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                }
            };
        } else if(setSortMode == 'I') {
            treeComparator = new Comparator<AccountTreeNode>() {
                public int compare(AccountTreeNode obj1, AccountTreeNode obj2) {
                    String obj1Icon = null, obj2Icon = null;
                    if(obj1 instanceof Account) {
                        obj1Icon = ((Account)obj1).getIcon();
                    } else {
                        obj1Icon = "AAAAAAAAAAAAAA";
                    }
                    if(obj2 instanceof Account) {
                        obj2Icon = ((Account)obj2).getIcon();
                    } else {
                        obj2Icon = "AAAAAAAAAAAAAA";
                    }
                    int sortOrder = obj1Icon.compareToIgnoreCase(obj2Icon);
                    if(setSortOrder == 'A') {
                        // Sort by name if dates are the same
                        if(obj1Icon.equals(obj2Icon)) {
                            return ((AccountTreeNode)obj1).getName().compareToIgnoreCase(((AccountTreeNode)obj2).getName());
                        } else {
                            return sortOrder;
                        }
                    } else {
                        if(sortOrder > 0) {
                            return -1;
                        } else if(sortOrder < 0) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                    
                }
            };
        }
    }
    
    class SortModeListener implements ActionListener {
        // Name, Icon, Type, Created, Modified
        public void actionPerformed(ActionEvent e) {
            if(itmSortName.isSelected()) {
                setSortMode = 'N';
            } else if(itmSortType.isSelected()) {
                setSortMode = 'T';
            } else if(itmSortCreated.isSelected()) {
                setSortMode = 'C';
            } else if(itmSortModified.isSelected()) {
                setSortMode = 'M';
            } else if(itmSortIcon.isSelected()) {
                setSortMode = 'I';
            }
            if(itmSortAscending.isSelected()) {
                setSortOrder = 'A';
            } else {
                setSortOrder = 'D';
            }
            refreshAccountTree();
        }
    }
    
    class GroupTableModel extends AbstractTableModel {
        private ArrayList<Account> data;
        private int iconSize, colWidth, rowHeight, numRows, numCols;
        private Component parent;
        
        public GroupTableModel(ArrayList<Account> data, Component parent, int iconSize, String mode) {
            this.data = data;
            this.iconSize = iconSize;
            this.parent = parent;
            if(mode.equals("tile")) {
                colWidth = this.iconSize*3;
                rowHeight = iconSize+8;
            } else if(setViewMode == 'L') {
                colWidth = iconSize+10;
                rowHeight = iconSize+25;
            } else if(setViewMode == 'S') {
                colWidth = iconSize+7;
                rowHeight = iconSize+22;
            }
            setupCols();
        }
        public void setupCols() {
            numCols = parent.getWidth()/colWidth;
            if(data.isEmpty()) {
                numRows = 1;
            } else if(data.size()%numCols == 0) {
                numRows = data.size()/numCols;
            } else {
                numRows = (data.size()/numCols)+1;
            }
        }
        public int getRowCount() {
            return numRows;
        }

        public int getColumnCount() {
            return numCols;
        }
        public int getColWidth() {
            return colWidth;
        }
        public int getRowHeight() {
            return rowHeight;
        }
        public int getIconSize() {
            return iconSize;
        }
        public Object getValueAt(int row, int col) {
            int index = numCols*row+col;
            if(index>=data.size()) {
                return "";
            } else {
                try {
                    return data.get(index);
                } catch(Exception e) {
                    return "";
                }
            }
        }
        @Override
        public String getColumnName(int index) {
            return "";
        }
    }
    class GroupTableListModel extends AbstractTableModel {
        private ArrayList<Account> data;
        //private String[] colNames = {"", "Name","Type","Group","Created","Modified"};
        private String[] colNames = {"", lang.getString("NAME"), lang.getString("TYPE"), lang.getString("GROUP"), lang.getString("CREATED"), lang.getString("MODIFIED") };
        
        public GroupTableListModel(ArrayList<Account> data) {
            this.data = data;
            
        }
        public int getRowCount() {
            return data.size();
        }

        public int getColumnCount() {
            return 6;
        }

        public Object getValueAt(int row, int col) {
            Account account = (Account)data.get(row);
            switch(col) {
                case 0:
                    return account.getIcon();
                case 1:
                    return account.getName();
                case 2:
                    return account.getType();
                case 3:
                    return account.getGroup();
                case 4:
                    return account.getDateCreated();
                case 5:
                    return account.getDateModified();
                default:
                    return "";
            }
        }
        @Override
        public String getColumnName(int index) {
            return colNames[index];
        }
    }
    class DateRenderer extends DefaultTableCellRenderer implements TableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(
                        JTable table, Object acc,
                        boolean isSelected, boolean hasFocus,
                        int row, int column) {
            Component cell = super.getTableCellRendererComponent(table, acc, isSelected, hasFocus, row, column);
            if(acc == null) {
                return null;
            } else {
                SimpleDateFormat df = new SimpleDateFormat(lang.getString("DATEFORMAT"));
                this.setText(df.format((java.util.Date)acc));
            }
            return this;
        }
    }
    class URLListener implements ActionListener {
         public void actionPerformed(ActionEvent e) {
            Utils.openURL(e.getActionCommand());
        }
    }
    class CopyListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            final String stringToCopy = e.getActionCommand();
            final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(new StringSelection(stringToCopy), null);
            if(setSecureClipboard) {
                new javax.swing.Timer(setSecureClipDelay*1000, new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        String data = null;
                        try {
                            data = (String)clipboard.getContents(this).getTransferData(new DataFlavor(String.class, "text"));
                        } catch(Exception ignore) {/**/}
                        if(data.equals(stringToCopy)) {
                            clipboard.setContents(new StringSelection(""), null);
                        }
                        ((javax.swing.Timer)e.getSource()).stop();
                    }
                }).start();
            }
        }
    }
    class PeekListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            final String stringToShow = e.getActionCommand();
            JOptionPane.showMessageDialog(null, stringToShow, lang.getString("REVEALPASSWORD"), JOptionPane.INFORMATION_MESSAGE);
        }
    }
}
